1
0
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:
David Baker
2018-10-24 19:15:04 +01:00
12 changed files with 218 additions and 199 deletions

View File

@@ -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)

View File

@@ -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?
---------------------- ----------------------

View File

@@ -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": [

View File

@@ -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,

View File

@@ -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();

View File

@@ -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);
}) })

View File

@@ -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}` +

View File

@@ -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;

View File

@@ -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

View File

@@ -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.

View File

@@ -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();

View File

@@ -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);
}
}; };
/** /**