Initial support for notification settings

Signed-off-by: Travis Ralston <travpc@gmail.com>
This commit is contained in:
Travis Ralston 2017-11-04 21:47:18 -07:00
parent 8351ec7e73
commit 7ce4316cc8
16 changed files with 194 additions and 105 deletions

View file

@ -0,0 +1,62 @@
/*
Copyright 2017 Travis Ralston
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import SettingsHandler from "./SettingsHandler";
import MatrixClientPeg from '../../MatrixClientPeg';
/**
* Gets and sets settings at the "account" level for the current user.
* This handler does not make use of the roomId parameter.
*/
export default class AccountSettingHandler extends SettingsHandler {
getValue(settingName, roomId) {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
const content = this._getSettings("org.matrix.preview_urls");
return !content['disable'];
}
return this._getSettings()[settingName];
}
setValue(settingName, roomId, newValue) {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
const content = this._getSettings("org.matrix.preview_urls");
content['disable'] = !newValue;
return MatrixClientPeg.get().setAccountData("org.matrix.preview_urls", content);
}
const content = this._getSettings();
content[settingName] = newValue;
return MatrixClientPeg.get().setAccountData("im.vector.web.settings", content);
}
canSetValue(settingName, roomId) {
return true; // It's their account, so they should be able to
}
isSupported() {
const cli = MatrixClientPeg.get();
return cli !== undefined && cli !== null;
}
_getSettings(eventType = "im.vector.web.settings") {
const event = MatrixClientPeg.get().getAccountData(eventType);
if (!event || !event.getContent()) return {};
return event.getContent();
}
}

View file

@ -0,0 +1,50 @@
/*
Copyright 2017 Travis Ralston
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Promise from 'bluebird';
import SettingsHandler from "./SettingsHandler";
import SdkConfig from "../../SdkConfig";
/**
* Gets and sets settings at the "config" level. This handler does not make use of the
* roomId parameter.
*/
export default class ConfigSettingsHandler extends SettingsHandler {
getValue(settingName, roomId) {
const config = SdkConfig.get() || {};
// Special case themes
if (settingName === "theme") {
return config["default_theme"];
}
const settingsConfig = config["settingDefaults"];
if (!settingsConfig || !settingsConfig[settingName]) return null;
return settingsConfig[settingName];
}
setValue(settingName, roomId, newValue) {
throw new Error("Cannot change settings at the config level");
}
canSetValue(settingName, roomId) {
return false;
}
isSupported() {
return true; // SdkConfig is always there
}
}

View file

@ -0,0 +1,48 @@
/*
Copyright 2017 Travis Ralston
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import SettingsHandler from "./SettingsHandler";
/**
* Gets settings at the "default" level. This handler does not support setting values.
* This handler does not make use of the roomId parameter.
*/
export default class DefaultSettingsHandler extends SettingsHandler {
/**
* Creates a new default settings handler with the given defaults
* @param {object} defaults The default setting values, keyed by setting name.
*/
constructor(defaults) {
super();
this._defaults = defaults;
}
getValue(settingName, roomId) {
return this._defaults[settingName];
}
setValue(settingName, roomId, newValue) {
throw new Error("Cannot set values on the default level handler");
}
canSetValue(settingName, roomId) {
return false;
}
isSupported() {
return true;
}
}

View file

@ -0,0 +1,113 @@
/*
Copyright 2017 Travis Ralston
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Promise from 'bluebird';
import SettingsHandler from "./SettingsHandler";
import MatrixClientPeg from "../../MatrixClientPeg";
/**
* Gets and sets settings at the "device" level for the current device.
* This handler does not make use of the roomId parameter. This handler
* will special-case features to support legacy settings.
*/
export default class DeviceSettingsHandler extends SettingsHandler {
/**
* Creates a new device settings handler
* @param {string[]} featureNames The names of known features.
*/
constructor(featureNames) {
super();
this._featureNames = featureNames;
}
getValue(settingName, roomId) {
if (this._featureNames.includes(settingName)) {
return this._readFeature(settingName);
}
// Special case notifications
if (settingName === "notificationsEnabled") {
return localStorage.getItem("notifications_enabled") === "true";
} else if (settingName === "notificationBodyEnabled") {
return localStorage.getItem("notifications_body_enabled") === "true";
} else if (settingName === "audioNotificationsEnabled") {
return localStorage.getItem("audio_notifications_enabled") === "true";
} else if (settingName === "notificationToolbarHidden") {
return localStorage.getItem("notifications_hidden") === "true";
}
return this._getSettings()[settingName];
}
setValue(settingName, roomId, newValue) {
if (this._featureNames.includes(settingName)) {
this._writeFeature(settingName, newValue);
return Promise.resolve();
}
// Special case notifications
if (settingName === "notificationsEnabled") {
localStorage.setItem("notifications_enabled", newValue);
return Promise.resolve();
} else if (settingName === "notificationBodyEnabled") {
localStorage.setItem("notifications_body_enabled", newValue);
return Promise.resolve();
} else if (settingName === "audioNotificationsEnabled") {
localStorage.setItem("audio_notifications_enabled", newValue);
return Promise.resolve();
} else if (settingName === "notificationToolbarHidden") {
localStorage.setItem("notifications_hidden", newValue);
return Promise.resolve();
}
const settings = this._getSettings();
settings[settingName] = newValue;
localStorage.setItem("mx_local_settings", JSON.stringify(settings));
return Promise.resolve();
}
canSetValue(settingName, roomId) {
return true; // It's their device, so they should be able to
}
isSupported() {
return localStorage !== undefined && localStorage !== null;
}
_getSettings() {
const value = localStorage.getItem("mx_local_settings");
if (!value) return {};
return JSON.parse(value);
}
// Note: features intentionally don't use the same key as settings to avoid conflicts
// and to be backwards compatible.
_readFeature(featureName) {
if (MatrixClientPeg.get() && MatrixClientPeg.get().isGuest()) {
// Guests should not have any labs features enabled.
return {enabled: false};
}
const value = localStorage.getItem("mx_labs_feature_" + featureName);
return value === "true";
}
_writeFeature(featureName, enabled) {
localStorage.setItem("mx_labs_feature_" + featureName, enabled);
}
}

View file

@ -0,0 +1,67 @@
/*
Copyright 2017 Travis Ralston
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import SettingsHandler from "./SettingsHandler";
/**
* A wrapper for a SettingsHandler that performs local echo on
* changes to settings. This wrapper will use the underlying
* handler as much as possible to ensure values are not stale.
*/
export default class LocalEchoWrapper extends SettingsHandler {
/**
* Creates a new local echo wrapper
* @param {SettingsHandler} handler The handler to wrap
*/
constructor(handler) {
super();
this._handler = handler;
this._cache = {
// settingName: { roomId: value }
};
}
getValue(settingName, roomId) {
const cacheRoomId = roomId ? roomId : "UNDEFINED"; // avoid weird keys
const bySetting = this._cache[settingName];
if (bySetting && bySetting.hasOwnProperty(cacheRoomId)) {
return bySetting[roomId];
}
return this._handler.getValue(settingName, roomId);
}
setValue(settingName, roomId, newValue) {
if (!this._cache[settingName]) this._cache[settingName] = {};
const bySetting = this._cache[settingName];
const cacheRoomId = roomId ? roomId : "UNDEFINED"; // avoid weird keys
bySetting[cacheRoomId] = newValue;
return this._handler.setValue(settingName, roomId, newValue).finally(() => {
delete bySetting[cacheRoomId];
});
}
canSetValue(settingName, roomId) {
return this._handler.canSetValue(settingName, roomId);
}
isSupported() {
return this._handler.isSupported();
}
}

View file

@ -0,0 +1,80 @@
/*
Copyright 2017 Travis Ralston
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import SettingsHandler from "./SettingsHandler";
import MatrixClientPeg from '../../MatrixClientPeg';
/**
* Gets and sets settings at the "room-account" level for the current user.
*/
export default class RoomAccountSettingsHandler extends SettingsHandler {
getValue(settingName, roomId) {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
const content = this._getSettings(roomId, "org.matrix.room.preview_urls");
return !content['disable'];
}
// Special case room color
if (settingName === "roomColor") {
// The event content should already be in an appropriate format, we just need
// to get the right value.
return this._getSettings(roomId, "org.matrix.room.color_scheme");
}
return this._getSettings(roomId)[settingName];
}
setValue(settingName, roomId, newValue) {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
const content = this._getSettings(roomId, "org.matrix.room.preview_urls");
content['disable'] = !newValue;
return MatrixClientPeg.get().setRoomAccountData(roomId, "org.matrix.room.preview_urls", content);
}
// Special case room color
if (settingName === "roomColor") {
// The new value should match our requirements, we just need to store it in the right place.
return MatrixClientPeg.get().setRoomAccountData(roomId, "org.matrix.room.color_scheme", newValue);
}
const content = this._getSettings(roomId);
content[settingName] = newValue;
return MatrixClientPeg.get().setRoomAccountData(roomId, "im.vector.web.settings", content);
}
canSetValue(settingName, roomId) {
const room = MatrixClientPeg.get().getRoom(roomId);
// If they have the room, they can set their own account data
return room !== undefined && room !== null;
}
isSupported() {
const cli = MatrixClientPeg.get();
return cli !== undefined && cli !== null;
}
_getSettings(roomId, eventType = "im.vector.settings") {
const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) return {};
const event = room.getAccountData(eventType);
if (!event || !event.getContent()) return {};
return event.getContent();
}
}

View file

@ -0,0 +1,77 @@
/*
Copyright 2017 Travis Ralston
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Promise from 'bluebird';
import SettingsHandler from "./SettingsHandler";
/**
* Gets and sets settings at the "room-device" level for the current device in a particular
* room.
*/
export default class RoomDeviceSettingsHandler extends SettingsHandler {
getValue(settingName, roomId) {
// Special case blacklist setting to use legacy values
if (settingName === "blacklistUnverifiedDevices") {
const value = this._read("mx_local_settings");
if (value && value['blacklistUnverifiedDevicesPerRoom']) {
return value['blacklistUnverifiedDevicesPerRoom'][roomId];
}
}
const value = this._read(this._getKey(settingName, roomId));
if (value) return value.value;
return null;
}
setValue(settingName, roomId, newValue) {
// Special case blacklist setting for legacy structure
if (settingName === "blacklistUnverifiedDevices") {
let value = this._read("mx_local_settings");
if (!value) value = {};
if (!value["blacklistUnverifiedDevicesPerRoom"]) value["blacklistUnverifiedDevicesPerRoom"] = {};
value["blacklistUnverifiedDevicesPerRoom"][roomId] = newValue;
localStorage.setItem("mx_local_settings", JSON.stringify(value));
return Promise.resolve();
}
if (newValue === null) {
localStorage.removeItem(this._getKey(settingName, roomId));
} else {
newValue = JSON.stringify({value: newValue});
localStorage.setItem(this._getKey(settingName, roomId), newValue);
}
return Promise.resolve();
}
canSetValue(settingName, roomId) {
return true; // It's their device, so they should be able to
}
isSupported() {
return localStorage !== undefined && localStorage !== null;
}
_read(key) {
const rawValue = localStorage.getItem(key);
if (!rawValue) return null;
return JSON.parse(rawValue);
}
_getKey(settingName, roomId) {
return "mx_setting_" + settingName + "_" + roomId;
}
}

View file

@ -0,0 +1,70 @@
/*
Copyright 2017 Travis Ralston
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import SettingsHandler from "./SettingsHandler";
import MatrixClientPeg from '../../MatrixClientPeg';
/**
* Gets and sets settings at the "room" level.
*/
export default class RoomSettingsHandler extends SettingsHandler {
getValue(settingName, roomId) {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
const content = this._getSettings(roomId, "org.matrix.room.preview_urls");
return !content['disable'];
}
return this._getSettings(roomId)[settingName];
}
setValue(settingName, roomId, newValue) {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
const content = this._getSettings(roomId, "org.matrix.room.preview_urls");
content['disable'] = !newValue;
return MatrixClientPeg.get().sendStateEvent(roomId, "org.matrix.room.preview_urls", content);
}
const content = this._getSettings(roomId);
content[settingName] = newValue;
return MatrixClientPeg.get().sendStateEvent(roomId, "im.vector.web.settings", content, "");
}
canSetValue(settingName, roomId) {
const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId);
let eventType = "im.vector.web.settings";
if (settingName === "urlPreviewsEnabled") eventType = "org.matrix.room.preview_urls";
if (!room) return false;
return room.currentState.maySendStateEvent(eventType, cli.getUserId());
}
isSupported() {
const cli = MatrixClientPeg.get();
return cli !== undefined && cli !== null;
}
_getSettings(roomId, eventType = "im.vector.web.settings") {
const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) return {};
const event = room.currentState.getStateEvents(eventType, "");
if (!event || !event.getContent()) return {};
return event.getContent();
}
}

View file

@ -0,0 +1,67 @@
/*
Copyright 2017 Travis Ralston
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* Represents the base class for all level handlers. This class performs no logic
* and should be overridden.
*/
export default class SettingsHandler {
/**
* Gets the value for a particular setting at this level for a particular room.
* If no room is applicable, the roomId may be null. The roomId may not be
* applicable to this level and may be ignored by the handler.
* @param {string} settingName The name of the setting.
* @param {String} roomId The room ID to read from, may be null.
* @returns {*} The setting value, or null if not found.
*/
getValue(settingName, roomId) {
throw new Error("Operation not possible: getValue was not overridden");
}
/**
* Sets the value for a particular setting at this level for a particular room.
* If no room is applicable, the roomId may be null. The roomId may not be
* applicable to this level and may be ignored by the handler. Setting a value
* to null will cause the level to remove the value. The current user should be
* able to set the value prior to calling this.
* @param {string} settingName The name of the setting to change.
* @param {String} roomId The room ID to set the value in, may be null.
* @param {*} newValue The new value for the setting, may be null.
* @returns {Promise} Resolves when the setting has been saved.
*/
setValue(settingName, roomId, newValue) {
throw new Error("Operation not possible: setValue was not overridden");
}
/**
* Determines if the current user is able to set the value of the given setting
* in the given room at this level.
* @param {string} settingName The name of the setting to check.
* @param {String} roomId The room ID to check in, may be null
* @returns {boolean} True if the setting can be set by the user, false otherwise.
*/
canSetValue(settingName, roomId) {
return false;
}
/**
* Determines if this level is supported on this device.
* @returns {boolean} True if this level is supported on the current device.
*/
isSupported() {
return false;
}
}