You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-28 05:03:59 +03:00
Merge branch 'develop' into dbkr/cross_signing
This commit is contained in:
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
patreon: matrixdotorg
|
||||||
|
liberapay: matrixdotorg
|
||||||
@@ -296,7 +296,7 @@ Then visit ``http://localhost:8005`` to see the API docs.
|
|||||||
End-to-end encryption support
|
End-to-end encryption support
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
The SDK supports end-to-end encryption via the and Megolm protocols, using
|
The SDK supports end-to-end encryption via the Olm and Megolm protocols, using
|
||||||
[libolm](https://gitlab.matrix.org/matrix-org/olm). It is left up to the
|
[libolm](https://gitlab.matrix.org/matrix-org/olm). It is left up to the
|
||||||
application to make libolm available, via the ``Olm`` global.
|
application to make libolm available, via the ``Olm`` global.
|
||||||
|
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ describe("AutoDiscovery", function() {
|
|||||||
"m.homeserver": {
|
"m.homeserver": {
|
||||||
state: "FAIL_ERROR",
|
state: "FAIL_ERROR",
|
||||||
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
||||||
base_url: null,
|
base_url: "https://example.org",
|
||||||
},
|
},
|
||||||
"m.identity_server": {
|
"m.identity_server": {
|
||||||
state: "PROMPT",
|
state: "PROMPT",
|
||||||
@@ -304,7 +304,7 @@ describe("AutoDiscovery", function() {
|
|||||||
"m.homeserver": {
|
"m.homeserver": {
|
||||||
state: "FAIL_ERROR",
|
state: "FAIL_ERROR",
|
||||||
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
||||||
base_url: null,
|
base_url: "https://example.org",
|
||||||
},
|
},
|
||||||
"m.identity_server": {
|
"m.identity_server": {
|
||||||
state: "PROMPT",
|
state: "PROMPT",
|
||||||
@@ -335,7 +335,7 @@ describe("AutoDiscovery", function() {
|
|||||||
"m.homeserver": {
|
"m.homeserver": {
|
||||||
state: "FAIL_ERROR",
|
state: "FAIL_ERROR",
|
||||||
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
error: AutoDiscovery.ERROR_INVALID_HOMESERVER,
|
||||||
base_url: null,
|
base_url: "https://example.org",
|
||||||
},
|
},
|
||||||
"m.identity_server": {
|
"m.identity_server": {
|
||||||
state: "PROMPT",
|
state: "PROMPT",
|
||||||
@@ -528,7 +528,7 @@ describe("AutoDiscovery", function() {
|
|||||||
"m.identity_server": {
|
"m.identity_server": {
|
||||||
state: "FAIL_ERROR",
|
state: "FAIL_ERROR",
|
||||||
error: AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
|
error: AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
|
||||||
base_url: null,
|
base_url: "https://identity.example.org",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -569,7 +569,7 @@ describe("AutoDiscovery", function() {
|
|||||||
"m.identity_server": {
|
"m.identity_server": {
|
||||||
state: "FAIL_ERROR",
|
state: "FAIL_ERROR",
|
||||||
error: AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
|
error: AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
|
||||||
base_url: null,
|
base_url: "https://identity.example.org",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ describe("MatrixScheduler", function() {
|
|||||||
clock.uninstall();
|
clock.uninstall();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should process events in a queue in a FIFO manner", function(done) {
|
it("should process events in a queue in a FIFO manner", async function() {
|
||||||
retryFn = function() {
|
retryFn = function() {
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
@@ -57,28 +57,30 @@ describe("MatrixScheduler", function() {
|
|||||||
};
|
};
|
||||||
const deferA = Promise.defer();
|
const deferA = Promise.defer();
|
||||||
const deferB = Promise.defer();
|
const deferB = Promise.defer();
|
||||||
let resolvedA = false;
|
let yieldedA = false;
|
||||||
scheduler.setProcessFunction(function(event) {
|
scheduler.setProcessFunction(function(event) {
|
||||||
if (resolvedA) {
|
if (yieldedA) {
|
||||||
expect(event).toEqual(eventB);
|
expect(event).toEqual(eventB);
|
||||||
return deferB.promise;
|
return deferB.promise;
|
||||||
} else {
|
} else {
|
||||||
|
yieldedA = true;
|
||||||
expect(event).toEqual(eventA);
|
expect(event).toEqual(eventA);
|
||||||
return deferA.promise;
|
return deferA.promise;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
scheduler.queueEvent(eventA);
|
const abPromise = Promise.all([
|
||||||
scheduler.queueEvent(eventB).done(function() {
|
scheduler.queueEvent(eventA),
|
||||||
expect(resolvedA).toBe(true);
|
scheduler.queueEvent(eventB),
|
||||||
done();
|
]);
|
||||||
});
|
deferB.resolve({b: true});
|
||||||
deferA.resolve({});
|
deferA.resolve({a: true});
|
||||||
resolvedA = true;
|
const [a, b] = await abPromise;
|
||||||
deferB.resolve({});
|
expect(a.a).toEqual(true);
|
||||||
|
expect(b.b).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should invoke the retryFn on failure and wait the amount of time specified",
|
it("should invoke the retryFn on failure and wait the amount of time specified",
|
||||||
function(done) {
|
async function() {
|
||||||
const waitTimeMs = 1500;
|
const waitTimeMs = 1500;
|
||||||
const retryDefer = Promise.defer();
|
const retryDefer = Promise.defer();
|
||||||
retryFn = function() {
|
retryFn = function() {
|
||||||
@@ -97,24 +99,26 @@ describe("MatrixScheduler", function() {
|
|||||||
return defer.promise;
|
return defer.promise;
|
||||||
} else if (procCount === 2) {
|
} else if (procCount === 2) {
|
||||||
// don't care about this defer
|
// don't care about this defer
|
||||||
return Promise.defer().promise;
|
return new Promise();
|
||||||
}
|
}
|
||||||
expect(procCount).toBeLessThan(3);
|
expect(procCount).toBeLessThan(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
scheduler.queueEvent(eventA);
|
scheduler.queueEvent(eventA);
|
||||||
|
// as queueing doesn't start processing synchronously anymore (see commit bbdb5ac)
|
||||||
|
// wait just long enough before it does
|
||||||
|
await Promise.resolve();
|
||||||
expect(procCount).toEqual(1);
|
expect(procCount).toEqual(1);
|
||||||
defer.reject({});
|
defer.reject({});
|
||||||
retryDefer.promise.done(function() {
|
await retryDefer.promise;
|
||||||
expect(procCount).toEqual(1);
|
expect(procCount).toEqual(1);
|
||||||
clock.tick(waitTimeMs);
|
clock.tick(waitTimeMs);
|
||||||
|
await Promise.resolve();
|
||||||
expect(procCount).toEqual(2);
|
expect(procCount).toEqual(2);
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should give up if the retryFn on failure returns -1 and try the next event",
|
it("should give up if the retryFn on failure returns -1 and try the next event",
|
||||||
function(done) {
|
async function() {
|
||||||
// Queue A & B.
|
// Queue A & B.
|
||||||
// Reject A and return -1 on retry.
|
// Reject A and return -1 on retry.
|
||||||
// Expect B to be tried next and the promise for A to be rejected.
|
// Expect B to be tried next and the promise for A to be rejected.
|
||||||
@@ -123,7 +127,7 @@ describe("MatrixScheduler", function() {
|
|||||||
};
|
};
|
||||||
queueFn = function() {
|
queueFn = function() {
|
||||||
return "yep";
|
return "yep";
|
||||||
};
|
};
|
||||||
|
|
||||||
const deferA = Promise.defer();
|
const deferA = Promise.defer();
|
||||||
const deferB = Promise.defer();
|
const deferB = Promise.defer();
|
||||||
@@ -142,13 +146,17 @@ describe("MatrixScheduler", function() {
|
|||||||
|
|
||||||
const globalA = scheduler.queueEvent(eventA);
|
const globalA = scheduler.queueEvent(eventA);
|
||||||
scheduler.queueEvent(eventB);
|
scheduler.queueEvent(eventB);
|
||||||
|
// as queueing doesn't start processing synchronously anymore (see commit bbdb5ac)
|
||||||
|
// wait just long enough before it does
|
||||||
|
await Promise.resolve();
|
||||||
expect(procCount).toEqual(1);
|
expect(procCount).toEqual(1);
|
||||||
deferA.reject({});
|
deferA.reject({});
|
||||||
globalA.catch(function() {
|
try {
|
||||||
|
await globalA;
|
||||||
|
} catch(err) {
|
||||||
|
await Promise.resolve();
|
||||||
expect(procCount).toEqual(2);
|
expect(procCount).toEqual(2);
|
||||||
done();
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should treat each queue separately", function(done) {
|
it("should treat each queue separately", function(done) {
|
||||||
@@ -300,8 +308,12 @@ describe("MatrixScheduler", function() {
|
|||||||
expect(ev).toEqual(eventA);
|
expect(ev).toEqual(eventA);
|
||||||
return defer.promise;
|
return defer.promise;
|
||||||
});
|
});
|
||||||
|
// as queueing doesn't start processing synchronously anymore (see commit bbdb5ac)
|
||||||
|
// wait just long enough before it does
|
||||||
|
Promise.resolve().then(() => {
|
||||||
expect(procCount).toEqual(1);
|
expect(procCount).toEqual(1);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("should not call the processFn if there are no queued events", function() {
|
it("should not call the processFn if there are no queued events", function() {
|
||||||
queueFn = function() {
|
queueFn = function() {
|
||||||
|
|||||||
@@ -256,6 +256,11 @@ export class AutoDiscovery {
|
|||||||
if (!hsVersions || !hsVersions.raw["versions"]) {
|
if (!hsVersions || !hsVersions.raw["versions"]) {
|
||||||
logger.error("Invalid /versions response");
|
logger.error("Invalid /versions response");
|
||||||
clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID_HOMESERVER;
|
clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID_HOMESERVER;
|
||||||
|
|
||||||
|
// Supply the base_url to the caller because they may be ignoring liveliness
|
||||||
|
// errors, like this one.
|
||||||
|
clientConfig["m.homeserver"].base_url = hsUrl;
|
||||||
|
|
||||||
return Promise.resolve(clientConfig);
|
return Promise.resolve(clientConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,6 +316,11 @@ export class AutoDiscovery {
|
|||||||
logger.error("Invalid /api/v1 response");
|
logger.error("Invalid /api/v1 response");
|
||||||
failingClientConfig["m.identity_server"].error =
|
failingClientConfig["m.identity_server"].error =
|
||||||
AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER;
|
AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER;
|
||||||
|
|
||||||
|
// Supply the base_url to the caller because they may be ignoring
|
||||||
|
// liveliness errors, like this one.
|
||||||
|
failingClientConfig["m.identity_server"].base_url = isUrl;
|
||||||
|
|
||||||
return Promise.resolve(failingClientConfig);
|
return Promise.resolve(failingClientConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,13 +151,14 @@ MatrixBaseApis.prototype.isUsernameAvailable = function(username) {
|
|||||||
* threepid uses during registration in the ID server. Set 'msisdn' to
|
* threepid uses during registration in the ID server. Set 'msisdn' to
|
||||||
* true to bind msisdn.
|
* true to bind msisdn.
|
||||||
* @param {string} guestAccessToken
|
* @param {string} guestAccessToken
|
||||||
|
* @param {string} inhibitLogin
|
||||||
* @param {module:client.callback} callback Optional.
|
* @param {module:client.callback} callback Optional.
|
||||||
* @return {module:client.Promise} Resolves: TODO
|
* @return {module:client.Promise} Resolves: TODO
|
||||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||||
*/
|
*/
|
||||||
MatrixBaseApis.prototype.register = function(
|
MatrixBaseApis.prototype.register = function(
|
||||||
username, password,
|
username, password,
|
||||||
sessionId, auth, bindThreepids, guestAccessToken,
|
sessionId, auth, bindThreepids, guestAccessToken, inhibitLogin,
|
||||||
callback,
|
callback,
|
||||||
) {
|
) {
|
||||||
// backwards compat
|
// backwards compat
|
||||||
@@ -166,6 +167,10 @@ MatrixBaseApis.prototype.register = function(
|
|||||||
} else if (bindThreepids === null || bindThreepids === undefined) {
|
} else if (bindThreepids === null || bindThreepids === undefined) {
|
||||||
bindThreepids = {};
|
bindThreepids = {};
|
||||||
}
|
}
|
||||||
|
if (typeof inhibitLogin === 'function') {
|
||||||
|
callback = inhibitLogin;
|
||||||
|
inhibitLogin = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (auth === undefined || auth === null) {
|
if (auth === undefined || auth === null) {
|
||||||
auth = {};
|
auth = {};
|
||||||
@@ -192,6 +197,9 @@ MatrixBaseApis.prototype.register = function(
|
|||||||
if (guestAccessToken !== undefined && guestAccessToken !== null) {
|
if (guestAccessToken !== undefined && guestAccessToken !== null) {
|
||||||
params.guest_access_token = guestAccessToken;
|
params.guest_access_token = guestAccessToken;
|
||||||
}
|
}
|
||||||
|
if (inhibitLogin !== undefined && inhibitLogin !== null) {
|
||||||
|
params.inhibit_login = inhibitLogin;
|
||||||
|
}
|
||||||
// Temporary parameter added to make the register endpoint advertise
|
// Temporary parameter added to make the register endpoint advertise
|
||||||
// msisdn flows. This exists because there are clients that break
|
// msisdn flows. This exists because there are clients that break
|
||||||
// when given stages they don't recognise. This parameter will cease
|
// when given stages they don't recognise. This parameter will cease
|
||||||
|
|||||||
@@ -740,19 +740,19 @@ async function _setDeviceVerification(
|
|||||||
* Request a key verification from another user.
|
* Request a key verification from another user.
|
||||||
*
|
*
|
||||||
* @param {string} userId the user to request verification with
|
* @param {string} userId the user to request verification with
|
||||||
* @param {Array} devices array of device IDs to send requests to. Defaults to
|
|
||||||
* all devices owned by the user
|
|
||||||
* @param {Array} methods array of verification methods to use. Defaults to
|
* @param {Array} methods array of verification methods to use. Defaults to
|
||||||
* all known methods
|
* all known methods
|
||||||
|
* @param {Array} devices array of device IDs to send requests to. Defaults to
|
||||||
|
* all devices owned by the user
|
||||||
*
|
*
|
||||||
* @returns {Promise<module:crypto/verification/Base>} resolves to a verifier
|
* @returns {Promise<module:crypto/verification/Base>} resolves to a verifier
|
||||||
* when the request is accepted by the other user
|
* when the request is accepted by the other user
|
||||||
*/
|
*/
|
||||||
MatrixClient.prototype.requestVerification = function(userId, devices, methods) {
|
MatrixClient.prototype.requestVerification = function(userId, methods, devices) {
|
||||||
if (this._crypto === null) {
|
if (this._crypto === null) {
|
||||||
throw new Error("End-to-end encryption disabled");
|
throw new Error("End-to-end encryption disabled");
|
||||||
}
|
}
|
||||||
return this._crypto.requestVerification(userId, devices);
|
return this._crypto.requestVerification(userId, methods, devices);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2047,6 +2047,9 @@ MatrixClient.prototype._sendCompleteEvent = function(roomId, eventObject, txnId,
|
|||||||
txnId = this.makeTxnId();
|
txnId = this.makeTxnId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we always construct a MatrixEvent when sending because the store and
|
||||||
|
// scheduler use them. We'll extract the params back out if it turns out
|
||||||
|
// the client has no scheduler or store.
|
||||||
const localEvent = new MatrixEvent(Object.assign(eventObject, {
|
const localEvent = new MatrixEvent(Object.assign(eventObject, {
|
||||||
event_id: "~" + roomId + ":" + txnId,
|
event_id: "~" + roomId + ":" + txnId,
|
||||||
user_id: this.credentials.userId,
|
user_id: this.credentials.userId,
|
||||||
@@ -2054,13 +2057,23 @@ MatrixClient.prototype._sendCompleteEvent = function(roomId, eventObject, txnId,
|
|||||||
origin_server_ts: new Date().getTime(),
|
origin_server_ts: new Date().getTime(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const room = this.getRoom(roomId);
|
||||||
|
|
||||||
|
// if this is a relation or redaction of an event
|
||||||
|
// that hasn't been sent yet (e.g. with a local id starting with a ~)
|
||||||
|
// then listen for the remote echo of that event so that by the time
|
||||||
|
// this event does get sent, we have the correct event_id
|
||||||
|
const targetId = localEvent.getAssociatedId();
|
||||||
|
if (targetId && targetId.startsWith("~")) {
|
||||||
|
const target = room.getPendingEvents().find(e => e.getId() === targetId);
|
||||||
|
target.once("Event.localEventIdReplaced", () => {
|
||||||
|
localEvent.updateAssociatedId(target.getId());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const type = localEvent.getType();
|
const type = localEvent.getType();
|
||||||
logger.log(`sendEvent of type ${type} in ${roomId} with txnId ${txnId}`);
|
logger.log(`sendEvent of type ${type} in ${roomId} with txnId ${txnId}`);
|
||||||
|
|
||||||
// we always construct a MatrixEvent when sending because the store and
|
|
||||||
// scheduler use them. We'll extract the params back out if it turns out
|
|
||||||
// the client has no scheduler or store.
|
|
||||||
const room = this.getRoom(roomId);
|
|
||||||
localEvent._txnId = txnId;
|
localEvent._txnId = txnId;
|
||||||
localEvent.setStatus(EventStatus.SENDING);
|
localEvent.setStatus(EventStatus.SENDING);
|
||||||
|
|
||||||
@@ -2214,9 +2227,11 @@ function _sendEventHttpRequest(client, event) {
|
|||||||
pathTemplate = "/rooms/$roomId/state/$eventType/$stateKey";
|
pathTemplate = "/rooms/$roomId/state/$eventType/$stateKey";
|
||||||
}
|
}
|
||||||
path = utils.encodeUri(pathTemplate, pathParams);
|
path = utils.encodeUri(pathTemplate, pathParams);
|
||||||
} else if (event.getType() === "m.room.redaction") {
|
} else if (event.isRedaction()) {
|
||||||
const pathTemplate = `/rooms/$roomId/redact/${event.event.redacts}/$txnId`;
|
const pathTemplate = `/rooms/$roomId/redact/$redactsEventId/$txnId`;
|
||||||
path = utils.encodeUri(pathTemplate, pathParams);
|
path = utils.encodeUri(pathTemplate, Object.assign({
|
||||||
|
$redactsEventId: event.event.redacts,
|
||||||
|
}, pathParams));
|
||||||
} else {
|
} else {
|
||||||
path = utils.encodeUri(
|
path = utils.encodeUri(
|
||||||
"/rooms/$roomId/send/$eventType/$txnId", pathParams,
|
"/rooms/$roomId/send/$eventType/$txnId", pathParams,
|
||||||
|
|||||||
@@ -693,7 +693,9 @@ Crypto.prototype.isKeyBackupTrusted = async function(backupInfo) {
|
|||||||
try {
|
try {
|
||||||
await olmlib.verifySignature(
|
await olmlib.verifySignature(
|
||||||
this._olmDevice,
|
this._olmDevice,
|
||||||
backupInfo.auth_data,
|
// verifySignature modifies the object so we need to copy
|
||||||
|
// if we verify more than one sig
|
||||||
|
Object.assign({}, backupInfo.auth_data),
|
||||||
this._userId,
|
this._userId,
|
||||||
device.deviceId,
|
device.deviceId,
|
||||||
device.getFingerprint(),
|
device.getFingerprint(),
|
||||||
@@ -2095,6 +2097,11 @@ Crypto.prototype._onKeyVerificationRequest = function(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sender = event.getSender();
|
const sender = event.getSender();
|
||||||
|
if (sender === this._userId && content.from_device === this._deviceId) {
|
||||||
|
// ignore requests from ourselves, because it doesn't make sense for a
|
||||||
|
// device to verify itself
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this._verificationTransactions.has(sender)) {
|
if (this._verificationTransactions.has(sender)) {
|
||||||
if (this._verificationTransactions.get(sender).has(content.transaction_id)) {
|
if (this._verificationTransactions.get(sender).has(content.transaction_id)) {
|
||||||
// transaction already exists: cancel it and drop the existing
|
// transaction already exists: cancel it and drop the existing
|
||||||
@@ -2147,7 +2154,7 @@ Crypto.prototype._onKeyVerificationRequest = function(event) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// notify the application that of the verification request, so it can
|
// notify the application of the verification request, so it can
|
||||||
// decide what to do with it
|
// decide what to do with it
|
||||||
const request = {
|
const request = {
|
||||||
event: event,
|
event: event,
|
||||||
|
|||||||
@@ -165,9 +165,21 @@ module.exports.MatrixHttpApi.prototype = {
|
|||||||
const contentType = opts.type || file.type || 'application/octet-stream';
|
const contentType = opts.type || file.type || 'application/octet-stream';
|
||||||
const fileName = opts.name || file.name;
|
const fileName = opts.name || file.name;
|
||||||
|
|
||||||
// we used to recommend setting file.stream to the thing to upload on
|
// We used to recommend setting file.stream to the thing to upload on
|
||||||
// nodejs.
|
// Node.js. As of 2019-06-11, this is still in widespread use in various
|
||||||
const body = file.stream ? file.stream : file;
|
// clients, so we should preserve this for simple objects used in
|
||||||
|
// Node.js. File API objects (via either the File or Blob interfaces) in
|
||||||
|
// the browser now define a `stream` method, which leads to trouble
|
||||||
|
// here, so we also check the type of `stream`.
|
||||||
|
let body = file;
|
||||||
|
if (body.stream && typeof body.stream !== "function") {
|
||||||
|
logger.warn(
|
||||||
|
"Using `file.stream` as the content to upload. Future " +
|
||||||
|
"versions of the js-sdk will change this to expect `file` to " +
|
||||||
|
"be the content directly.",
|
||||||
|
);
|
||||||
|
body = body.stream;
|
||||||
|
}
|
||||||
|
|
||||||
// backwards-compatibility hacks where we used to do different things
|
// backwards-compatibility hacks where we used to do different things
|
||||||
// between browser and node.
|
// between browser and node.
|
||||||
|
|||||||
@@ -49,11 +49,18 @@ const MSISDN_STAGE_TYPE = "m.login.msisdn";
|
|||||||
* @param {object?} opts.authData error response from the last request. If
|
* @param {object?} opts.authData error response from the last request. If
|
||||||
* null, a request will be made with no auth before starting.
|
* null, a request will be made with no auth before starting.
|
||||||
*
|
*
|
||||||
* @param {function(object?, bool?): module:client.Promise} opts.doRequest
|
* @param {function(object?): module:client.Promise} opts.doRequest
|
||||||
* called with the new auth dict to submit the request and a flag set
|
* called with the new auth dict to submit the request. Also passes a
|
||||||
* to true if this request is a background request. Should return a
|
* second deprecated arg which is a flag set to true if this request
|
||||||
* promise which resolves to the successful response or rejects with a
|
* is a background request. The busyChanged callback should be used
|
||||||
* MatrixError.
|
* instead of the backfround flag. Should return a promise which resolves
|
||||||
|
* to the successful response or rejects with a MatrixError.
|
||||||
|
*
|
||||||
|
* @param {function(bool): module:client.Promise} opts.busyChanged
|
||||||
|
* called whenever the interactive auth logic becomes busy submitting
|
||||||
|
* information provided by the user or finsihes. After this has been
|
||||||
|
* called with true the UI should indicate that a request is in progress
|
||||||
|
* until it is called again with false.
|
||||||
*
|
*
|
||||||
* @param {function(string, object?)} opts.stateUpdated
|
* @param {function(string, object?)} opts.stateUpdated
|
||||||
* called when the status of the UI auth changes, ie. when the state of
|
* called when the status of the UI auth changes, ie. when the state of
|
||||||
@@ -101,6 +108,7 @@ function InteractiveAuth(opts) {
|
|||||||
this._matrixClient = opts.matrixClient;
|
this._matrixClient = opts.matrixClient;
|
||||||
this._data = opts.authData || {};
|
this._data = opts.authData || {};
|
||||||
this._requestCallback = opts.doRequest;
|
this._requestCallback = opts.doRequest;
|
||||||
|
this._busyChangedCallback = opts.busyChanged;
|
||||||
// startAuthStage included for backwards compat
|
// startAuthStage included for backwards compat
|
||||||
this._stateUpdatedCallback = opts.stateUpdated || opts.startAuthStage;
|
this._stateUpdatedCallback = opts.stateUpdated || opts.startAuthStage;
|
||||||
this._resolveFunc = null;
|
this._resolveFunc = null;
|
||||||
@@ -112,9 +120,14 @@ function InteractiveAuth(opts) {
|
|||||||
this._clientSecret = opts.clientSecret || this._matrixClient.generateClientSecret();
|
this._clientSecret = opts.clientSecret || this._matrixClient.generateClientSecret();
|
||||||
this._emailSid = opts.emailSid;
|
this._emailSid = opts.emailSid;
|
||||||
if (this._emailSid === undefined) this._emailSid = null;
|
if (this._emailSid === undefined) this._emailSid = null;
|
||||||
|
this._requestingEmailToken = false;
|
||||||
|
|
||||||
this._chosenFlow = null;
|
this._chosenFlow = null;
|
||||||
this._currentStage = null;
|
this._currentStage = null;
|
||||||
|
|
||||||
|
// if we are currently trying to submit an auth dict (which includes polling)
|
||||||
|
// the promise the will resolve/reject when it completes
|
||||||
|
this._submitPromise = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
InteractiveAuth.prototype = {
|
InteractiveAuth.prototype = {
|
||||||
@@ -135,7 +148,10 @@ InteractiveAuth.prototype = {
|
|||||||
// if we have no flows, try a request (we'll have
|
// if we have no flows, try a request (we'll have
|
||||||
// just a session ID in _data if resuming)
|
// just a session ID in _data if resuming)
|
||||||
if (!this._data.flows) {
|
if (!this._data.flows) {
|
||||||
this._doRequest(this._data);
|
if (this._busyChangedCallback) this._busyChangedCallback(true);
|
||||||
|
this._doRequest(this._data).finally(() => {
|
||||||
|
if (this._busyChangedCallback) this._busyChangedCallback(false);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this._startNextAuthStage();
|
this._startNextAuthStage();
|
||||||
}
|
}
|
||||||
@@ -147,8 +163,11 @@ InteractiveAuth.prototype = {
|
|||||||
* completed out-of-band. If so, the attemptAuth promise will
|
* completed out-of-band. If so, the attemptAuth promise will
|
||||||
* be resolved.
|
* be resolved.
|
||||||
*/
|
*/
|
||||||
poll: function() {
|
poll: async function() {
|
||||||
if (!this._data.session) return;
|
if (!this._data.session) return;
|
||||||
|
// if we currently have a request in flight, there's no point making
|
||||||
|
// another just to check what the status is
|
||||||
|
if (this._submitPromise) return;
|
||||||
|
|
||||||
let authDict = {};
|
let authDict = {};
|
||||||
if (this._currentStage == EMAIL_STAGE_TYPE) {
|
if (this._currentStage == EMAIL_STAGE_TYPE) {
|
||||||
@@ -221,18 +240,44 @@ InteractiveAuth.prototype = {
|
|||||||
* in the attemptAuth promise being rejected. This can be set to true
|
* in the attemptAuth promise being rejected. This can be set to true
|
||||||
* for requests that just poll to see if auth has been completed elsewhere.
|
* for requests that just poll to see if auth has been completed elsewhere.
|
||||||
*/
|
*/
|
||||||
submitAuthDict: function(authData, background) {
|
submitAuthDict: async function(authData, background) {
|
||||||
if (!this._resolveFunc) {
|
if (!this._resolveFunc) {
|
||||||
throw new Error("submitAuthDict() called before attemptAuth()");
|
throw new Error("submitAuthDict() called before attemptAuth()");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!background && this._busyChangedCallback) {
|
||||||
|
this._busyChangedCallback(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we're currently trying a request, wait for it to finish
|
||||||
|
// as otherwise we can get multiple 200 responses which can mean
|
||||||
|
// things like multiple logins for register requests.
|
||||||
|
// (but discard any expections as we only care when its done,
|
||||||
|
// not whether it worked or not)
|
||||||
|
while (this._submitPromise) {
|
||||||
|
try {
|
||||||
|
await this._submitPromise;
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// use the sessionid from the last request.
|
// use the sessionid from the last request.
|
||||||
const auth = {
|
const auth = {
|
||||||
session: this._data.session,
|
session: this._data.session,
|
||||||
};
|
};
|
||||||
utils.extend(auth, authData);
|
utils.extend(auth, authData);
|
||||||
|
|
||||||
this._doRequest(auth, background);
|
try {
|
||||||
|
// NB. the 'background' flag is deprecated by the busyChanged
|
||||||
|
// callback and is here for backwards compat
|
||||||
|
this._submitPromise = this._doRequest(auth, background);
|
||||||
|
await this._submitPromise;
|
||||||
|
} finally {
|
||||||
|
this._submitPromise = null;
|
||||||
|
if (!background && this._busyChangedCallback) {
|
||||||
|
this._busyChangedCallback(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -305,12 +350,14 @@ InteractiveAuth.prototype = {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!this._emailSid &&
|
!this._emailSid &&
|
||||||
|
!this._requestingEmailToken &&
|
||||||
this._chosenFlow.stages.includes('m.login.email.identity')
|
this._chosenFlow.stages.includes('m.login.email.identity')
|
||||||
) {
|
) {
|
||||||
// If we've picked a flow with email auth, we send the email
|
// If we've picked a flow with email auth, we send the email
|
||||||
// now because we want the request to fail as soon as possible
|
// now because we want the request to fail as soon as possible
|
||||||
// if the email address is not valid (ie. already taken or not
|
// if the email address is not valid (ie. already taken or not
|
||||||
// registered, depending on what the operation is).
|
// registered, depending on what the operation is).
|
||||||
|
this._requestingEmailToken = true;
|
||||||
try {
|
try {
|
||||||
const requestTokenResult = await this._requestEmailTokenCallback(
|
const requestTokenResult = await this._requestEmailTokenCallback(
|
||||||
this._inputs.emailAddress,
|
this._inputs.emailAddress,
|
||||||
@@ -333,6 +380,8 @@ InteractiveAuth.prototype = {
|
|||||||
// the failure up as the user can't complete auth if we can't
|
// the failure up as the user can't complete auth if we can't
|
||||||
// send the email, foe whatever reason.
|
// send the email, foe whatever reason.
|
||||||
this._rejectFunc(e);
|
this._rejectFunc(e);
|
||||||
|
} finally {
|
||||||
|
this._requestingEmailToken = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ limitations under the License.
|
|||||||
const EventEmitter = require("events").EventEmitter;
|
const EventEmitter = require("events").EventEmitter;
|
||||||
const utils = require("../utils");
|
const utils = require("../utils");
|
||||||
const EventTimeline = require("./event-timeline");
|
const EventTimeline = require("./event-timeline");
|
||||||
|
import {EventStatus} from "./event";
|
||||||
import logger from '../../src/logger';
|
import logger from '../../src/logger';
|
||||||
import Relations from './relations';
|
import Relations from './relations';
|
||||||
|
|
||||||
@@ -749,6 +750,10 @@ EventTimelineSet.prototype.aggregateRelations = function(event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.isRedacted() || event.status === EventStatus.CANCELLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If the event is currently encrypted, wait until it has been decrypted.
|
// If the event is currently encrypted, wait until it has been decrypted.
|
||||||
if (event.isBeingDecrypted()) {
|
if (event.isBeingDecrypted()) {
|
||||||
event.once("Event.decrypted", () => {
|
event.once("Event.decrypted", () => {
|
||||||
|
|||||||
@@ -753,6 +753,15 @@ utils.extend(module.exports.MatrixEvent.prototype, {
|
|||||||
return Boolean(this.getUnsigned().redacted_because);
|
return Boolean(this.getUnsigned().redacted_because);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this event is a redaction of another event
|
||||||
|
*
|
||||||
|
* @return {boolean} True if this event is a redaction
|
||||||
|
*/
|
||||||
|
isRedaction: function() {
|
||||||
|
return this.getType() === "m.room.redaction";
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the push actions, if known, for this event
|
* Get the push actions, if known, for this event
|
||||||
*
|
*
|
||||||
@@ -776,9 +785,26 @@ utils.extend(module.exports.MatrixEvent.prototype, {
|
|||||||
* @param {Object} event the object to assign to the `event` property
|
* @param {Object} event the object to assign to the `event` property
|
||||||
*/
|
*/
|
||||||
handleRemoteEcho: function(event) {
|
handleRemoteEcho: function(event) {
|
||||||
|
const oldUnsigned = this.getUnsigned();
|
||||||
|
const oldId = this.getId();
|
||||||
this.event = event;
|
this.event = event;
|
||||||
|
// if this event was redacted before it was sent, it's locally marked as redacted.
|
||||||
|
// At this point, we've received the remote echo for the event, but not yet for
|
||||||
|
// the redaction that we are sending ourselves. Preserve the locally redacted
|
||||||
|
// state by copying over redacted_because so we don't get a flash of
|
||||||
|
// redacted, not-redacted, redacted as remote echos come in
|
||||||
|
if (oldUnsigned.redacted_because) {
|
||||||
|
if (!this.event.unsigned) {
|
||||||
|
this.event.unsigned = {};
|
||||||
|
}
|
||||||
|
this.event.unsigned.redacted_because = oldUnsigned.redacted_because;
|
||||||
|
}
|
||||||
// successfully sent.
|
// successfully sent.
|
||||||
this.setStatus(null);
|
this.setStatus(null);
|
||||||
|
if (this.getId() !== oldId) {
|
||||||
|
// emit the event if it changed
|
||||||
|
this.emit("Event.localEventIdReplaced", this);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -801,6 +827,11 @@ utils.extend(module.exports.MatrixEvent.prototype, {
|
|||||||
this.emit("Event.status", this, status);
|
this.emit("Event.status", this, status);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
replaceLocalEventId(eventId) {
|
||||||
|
this.event.event_id = eventId;
|
||||||
|
this.emit("Event.localEventIdReplaced", this);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get whether the event is a relation event, and of a given type if
|
* Get whether the event is a relation event, and of a given type if
|
||||||
* `relType` is passed in.
|
* `relType` is passed in.
|
||||||
@@ -876,6 +907,46 @@ utils.extend(module.exports.MatrixEvent.prototype, {
|
|||||||
return this._replacingEvent;
|
return this._replacingEvent;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For relations and redactions, returns the event_id this event is referring to.
|
||||||
|
*
|
||||||
|
* @return {string?}
|
||||||
|
*/
|
||||||
|
getAssociatedId() {
|
||||||
|
const relation = this.getRelation();
|
||||||
|
if (relation) {
|
||||||
|
return relation.event_id;
|
||||||
|
} else if (this.isRedaction()) {
|
||||||
|
return this.event.redacts;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this event is associated with another event. See `getAssociatedId`.
|
||||||
|
*
|
||||||
|
* @return {bool}
|
||||||
|
*/
|
||||||
|
hasAssocation() {
|
||||||
|
return !!this.getAssociatedId();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the related id with a new one.
|
||||||
|
*
|
||||||
|
* Used to replace a local id with remote one before sending
|
||||||
|
* an event with a related id.
|
||||||
|
*
|
||||||
|
* @param {string} eventId the new event id
|
||||||
|
*/
|
||||||
|
updateAssociatedId(eventId) {
|
||||||
|
const relation = this.getRelation();
|
||||||
|
if (relation) {
|
||||||
|
relation.event_id = eventId;
|
||||||
|
} else if (this.isRedaction()) {
|
||||||
|
this.event.redacts = eventId;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Summarise the event as JSON for debugging. If encrypted, include both the
|
* Summarise the event as JSON for debugging. If encrypted, include both the
|
||||||
* decrypted and encrypted view of the event. This is named `toJSON` for use
|
* decrypted and encrypted view of the event. This is named `toJSON` for use
|
||||||
|
|||||||
@@ -242,12 +242,7 @@ export default class Relations extends EventEmitter {
|
|||||||
|
|
||||||
redactedEvent.removeListener("Event.beforeRedaction", this._onBeforeRedaction);
|
redactedEvent.removeListener("Event.beforeRedaction", this._onBeforeRedaction);
|
||||||
|
|
||||||
// Dispatch a redaction event on this collection. `setTimeout` is used
|
|
||||||
// to wait until the next event loop iteration by which time the event
|
|
||||||
// has actually been marked as redacted.
|
|
||||||
setTimeout(() => {
|
|
||||||
this.emit("Relations.redaction");
|
this.emit("Relations.redaction");
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1032,7 +1032,7 @@ Room.prototype.removeFilteredTimelineSet = function(filter) {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
Room.prototype._addLiveEvent = function(event, duplicateStrategy) {
|
Room.prototype._addLiveEvent = function(event, duplicateStrategy) {
|
||||||
if (event.getType() === "m.room.redaction") {
|
if (event.isRedaction()) {
|
||||||
const redactId = event.event.redacts;
|
const redactId = event.event.redacts;
|
||||||
|
|
||||||
// if we know about this event, redact its contents now.
|
// if we know about this event, redact its contents now.
|
||||||
@@ -1141,9 +1141,13 @@ Room.prototype.addPendingEvent = function(event, txnId) {
|
|||||||
this._aggregateNonLiveRelation(event);
|
this._aggregateNonLiveRelation(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.getType() === "m.room.redaction") {
|
if (event.isRedaction()) {
|
||||||
const redactId = event.event.redacts;
|
const redactId = event.event.redacts;
|
||||||
const redactedEvent = this.getUnfilteredTimelineSet().findEventById(redactId);
|
let redactedEvent = this._pendingEventList &&
|
||||||
|
this._pendingEventList.find(e => e.getId() === redactId);
|
||||||
|
if (!redactedEvent) {
|
||||||
|
redactedEvent = this.getUnfilteredTimelineSet().findEventById(redactId);
|
||||||
|
}
|
||||||
if (redactedEvent) {
|
if (redactedEvent) {
|
||||||
redactedEvent.markLocallyRedacted(event);
|
redactedEvent.markLocallyRedacted(event);
|
||||||
this.emit("Room.redaction", event, this);
|
this.emit("Room.redaction", event, this);
|
||||||
@@ -1211,7 +1215,7 @@ Room.prototype._handleRemoteEcho = function(remoteEvent, localEvent) {
|
|||||||
const oldStatus = localEvent.status;
|
const oldStatus = localEvent.status;
|
||||||
|
|
||||||
// no longer pending
|
// no longer pending
|
||||||
delete this._txnToEvent[remoteEvent.transaction_id];
|
delete this._txnToEvent[remoteEvent.getUnsigned().transaction_id];
|
||||||
|
|
||||||
// if it's in the pending list, remove it
|
// if it's in the pending list, remove it
|
||||||
if (this._pendingEventList) {
|
if (this._pendingEventList) {
|
||||||
@@ -1315,7 +1319,7 @@ Room.prototype.updatePendingEvent = function(event, newStatus, newEventId) {
|
|||||||
|
|
||||||
if (newStatus == EventStatus.SENT) {
|
if (newStatus == EventStatus.SENT) {
|
||||||
// update the event id
|
// update the event id
|
||||||
event.event.event_id = newEventId;
|
event.replaceLocalEventId(newEventId);
|
||||||
|
|
||||||
// if the event was already in the timeline (which will be the case if
|
// if the event was already in the timeline (which will be the case if
|
||||||
// opts.pendingEventOrdering==chronological), we need to update the
|
// opts.pendingEventOrdering==chronological), we need to update the
|
||||||
@@ -1329,7 +1333,7 @@ Room.prototype.updatePendingEvent = function(event, newStatus, newEventId) {
|
|||||||
const idx = this._pendingEventList.findIndex(ev => ev.getId() === oldEventId);
|
const idx = this._pendingEventList.findIndex(ev => ev.getId() === oldEventId);
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
const [removedEvent] = this._pendingEventList.splice(idx, 1);
|
const [removedEvent] = this._pendingEventList.splice(idx, 1);
|
||||||
if (removedEvent.getType() === "m.room.redaction") {
|
if (removedEvent.isRedaction()) {
|
||||||
this._revertRedactionLocalEcho(removedEvent);
|
this._revertRedactionLocalEcho(removedEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1435,7 +1439,7 @@ Room.prototype.removeEvent = function(eventId) {
|
|||||||
for (let i = 0; i < this._timelineSets.length; i++) {
|
for (let i = 0; i < this._timelineSets.length; i++) {
|
||||||
const removed = this._timelineSets[i].removeEvent(eventId);
|
const removed = this._timelineSets[i].removeEvent(eventId);
|
||||||
if (removed) {
|
if (removed) {
|
||||||
if (removed.getType() === "m.room.redaction") {
|
if (removed.isRedaction()) {
|
||||||
this._revertRedactionLocalEcho(removed);
|
this._revertRedactionLocalEcho(removed);
|
||||||
}
|
}
|
||||||
removedAny = true;
|
removedAny = true;
|
||||||
|
|||||||
@@ -177,7 +177,8 @@ MatrixScheduler.RETRY_BACKOFF_RATELIMIT = function(event, attempts, err) {
|
|||||||
* @see module:scheduler~queueAlgorithm
|
* @see module:scheduler~queueAlgorithm
|
||||||
*/
|
*/
|
||||||
MatrixScheduler.QUEUE_MESSAGES = function(event) {
|
MatrixScheduler.QUEUE_MESSAGES = function(event) {
|
||||||
if (event.getType() === "m.room.message") {
|
// enqueue messages or events that associate with another event (redactions and relations)
|
||||||
|
if (event.getType() === "m.room.message" || event.hasAssocation()) {
|
||||||
// put these events in the 'message' queue.
|
// put these events in the 'message' queue.
|
||||||
return "message";
|
return "message";
|
||||||
}
|
}
|
||||||
@@ -220,7 +221,14 @@ function _processQueue(scheduler, queueName) {
|
|||||||
);
|
);
|
||||||
// fire the process function and if it resolves, resolve the deferred. Else
|
// fire the process function and if it resolves, resolve the deferred. Else
|
||||||
// invoke the retry algorithm.
|
// invoke the retry algorithm.
|
||||||
scheduler._procFn(obj.event).done(function(res) {
|
|
||||||
|
// First wait for a resolved promise, so the resolve handlers for
|
||||||
|
// the deferred of the previously sent event can run.
|
||||||
|
// This way enqueued relations/redactions to enqueued events can receive
|
||||||
|
// the remove id of their target before being sent.
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
return scheduler._procFn(obj.event);
|
||||||
|
}).then(function(res) {
|
||||||
// remove this from the queue
|
// remove this from the queue
|
||||||
_removeNextEvent(scheduler, queueName);
|
_removeNextEvent(scheduler, queueName);
|
||||||
debuglog("Queue '%s' sent event %s", queueName, obj.event.getId());
|
debuglog("Queue '%s' sent event %s", queueName, obj.event.getId());
|
||||||
|
|||||||
@@ -198,11 +198,13 @@ IndexedDBStore.prototype.wantsSave = function() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Possibly write data to the database.
|
* Possibly write data to the database.
|
||||||
|
*
|
||||||
|
* @param {bool} force True to force a save to happen
|
||||||
* @return {Promise} Promise resolves after the write completes
|
* @return {Promise} Promise resolves after the write completes
|
||||||
* (or immediately if no write is performed)
|
* (or immediately if no write is performed)
|
||||||
*/
|
*/
|
||||||
IndexedDBStore.prototype.save = function() {
|
IndexedDBStore.prototype.save = function(force) {
|
||||||
if (this.wantsSave()) {
|
if (force || this.wantsSave()) {
|
||||||
return this._reallySave();
|
return this._reallySave();
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|||||||
@@ -335,8 +335,10 @@ module.exports.MemoryStore.prototype = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Save does nothing as there is no backing data store.
|
* Save does nothing as there is no backing data store.
|
||||||
|
* @param {bool} force True to force a save (but the memory
|
||||||
|
* store still can't save anything)
|
||||||
*/
|
*/
|
||||||
save: function() {},
|
save: function(force) {},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Startup does nothing as this store doesn't require starting up.
|
* Startup does nothing as this store doesn't require starting up.
|
||||||
|
|||||||
Reference in New Issue
Block a user