more cleanup

This commit is contained in:
Audric Ackermann 2021-10-04 10:56:54 +11:00
parent d02d77a212
commit 1e8c8991ad
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4
54 changed files with 102 additions and 12692 deletions

View File

@ -1,12 +0,0 @@
{
"S3PublishEndpoints": {
"signal-desktop-apt": {
"region": "us-east-1",
"bucket": "updates.signal.org",
"prefix": "desktop/apt",
"acl": "public-read",
"plusWorkaround": false,
"disableMultiDel": false
}
}
}

View File

@ -32,8 +32,7 @@ js/util_worker.js
libtextsecure/test/blanket_mocha.js
mnemonic_languages/**
# Managed by package manager (`bower` and `yarn`/`npm`):
/bower.json
# Managed by package manager (`yarn`/`npm`):
/package.json
# Symlink into third-party `components`:

View File

@ -5,18 +5,32 @@ const sass = require('node-sass');
/* eslint-disable more/no-then, no-console */
const toConcatForApp = [
'node_modules/jquery/dist/jquery.js',
'node_modules/bytebuffer/dist/bytebuffer.min.js',
'node_modules/long/dist/long.js',
'components/protobuf/**/*.js',
'node_modules/mustache/mustache.js',
'node_modules/underscore/underscore.js',
'node_modules/backbone/backbone.js',
];
const toConcatForComponentTextsecure = [
'node_modules/long/dist/long.js',
'components/protobuf/**/*.js',
];
module.exports = grunt => {
const bower = grunt.file.readJSON('bower.json');
const components = [];
// eslint-disable-next-line guard-for-in, no-restricted-syntax
for (const i in bower.concat.app) {
components.push(bower.concat.app[i]);
for (const i in toConcatForApp) {
components.push(toConcatForApp[i]);
}
const libtextsecurecomponents = [];
// eslint-disable-next-line guard-for-in, no-restricted-syntax
for (const i in bower.concat.libtextsecure) {
libtextsecurecomponents.push(bower.concat.libtextsecure[i]);
for (const i in toConcatForComponentTextsecure) {
libtextsecurecomponents.push(toConcatForComponentTextsecure[i]);
}
const utilWorkerComponents = [
@ -58,13 +72,7 @@ module.exports = grunt => {
'libtextsecure/crypto.js',
'libtextsecure/storage.js',
'libtextsecure/storage/user.js',
'libtextsecure/storage/groups.js',
'libtextsecure/protobufs.js',
'libtextsecure/helpers.js',
'libtextsecure/stringview.js',
'libtextsecure/event_target.js',
'libtextsecure/http-resources.js',
'libtextsecure/message_receiver.js',
],
dest: 'js/libtextsecure.js',
},

View File

@ -453,5 +453,6 @@
"unableToCall": "cancel your ongoing call first",
"unableToCallTitle": "Cannot start new call",
"callMissed": "Missed call from $name$",
"callMissedTitle": "Call missed"
"callMissedTitle": "Call missed",
"startVideoCall": "Start Video Call"
}

View File

@ -32,9 +32,7 @@
-->
<script type="text/javascript" src="js/components.js"></script>
<script type="text/javascript" src="js/database.js"></script>
<script type="text/javascript" src="js/storage.js"></script>
<script type="text/javascript" src="js/legacy_storage.js"></script>
<script type="text/javascript" src="js/libtextsecure.js"></script>
<script type="text/javascript" src="js/focus_listener.js"></script>
@ -43,7 +41,6 @@
<script type="text/javascript" src="js/read_syncs.js"></script>
<script type="text/javascript" src="js/expiring_messages.js"></script>
<script type="text/javascript" src="js/chromium.js"></script>
<script type="text/javascript" src="js/registration.js"></script>
<script type="text/javascript" src="js/views/react_wrapper_view.js"></script>

View File

@ -32,9 +32,7 @@
-->
<script type="text/javascript" src="js/components.js"></script>
<script type="text/javascript" src="js/database.js"></script>
<script type="text/javascript" src="js/storage.js"></script>
<script type="text/javascript" src="js/legacy_storage.js"></script>
<script type="text/javascript" src="js/libtextsecure.js"></script>
<script type="text/javascript" src="js/focus_listener.js"></script>
@ -43,7 +41,6 @@
<script type="text/javascript" src="js/read_syncs.js"></script>
<script type="text/javascript" src="js/expiring_messages.js"></script>
<script type="text/javascript" src="js/chromium.js"></script>
<script type="text/javascript" src="js/registration.js"></script>
<script type="text/javascript" src="js/views/react_wrapper_view.js"></script>

View File

View File

@ -1,41 +0,0 @@
{
"name": "session-desktop",
"version": "0.0.0",
"homepage": "https://github.com/loki-project/session-desktop",
"license": "GPLV3",
"private": true,
"dependencies": {
"indexeddb-backbonejs-adapter": "*",
"protobuf": "~3.8.0"
},
"devDependencies": {
"mock-socket": "~0.3.2"
},
"preen": {
"indexeddb-backbonejs-adapter": [
"backbone-indexeddb.js"
],
"mock-socket": [
"dist/mock-socket.js"
],
"protobuf": [
"dist/ProtoBuf.js"
]
},
"concat": {
"app": [
"node_modules/jquery/dist/jquery.js",
"node_modules/bytebuffer/dist/bytebuffer.min.js",
"node_modules/long/dist/long.js",
"components/protobuf/**/*.js",
"node_modules/mustache/mustache.js",
"node_modules/underscore/underscore.js",
"node_modules/backbone/backbone.js",
"components/indexeddb-backbonejs-adapter/**/*.js"
],
"libtextsecure": [
"node_modules/long/dist/long.js",
"components/protobuf/**/*.js"
]
}
}

View File

@ -1,662 +0,0 @@
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['backbone', 'underscore'], factory);
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory(require('backbone'), require('underscore'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.Backbone, root._);
}
}(this, function (Backbone, _) {
// Generate four random hex digits.
function S4() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
// Generate a pseudo-GUID by concatenating random hexadecimal.
function guid() {
return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
}
if ( _(indexedDB).isUndefined() ) { return; }
// Driver object
// That's the interesting part.
// There is a driver for each schema provided. The schema is a te combination of name (for the database), a version as well as migrations to reach that
// version of the database.
function Driver(schema, ready, nolog, onerror) {
this.schema = schema;
this.ready = ready;
this.error = null;
this.transactions = []; // Used to list all transactions and keep track of active ones.
this.db = null;
this.nolog = nolog;
this.onerror = onerror;
var lastMigrationPathVersion = _.last(this.schema.migrations).version;
if (!this.nolog) debugLog("opening database " + this.schema.id + " in version #" + lastMigrationPathVersion);
this.dbRequest = indexedDB.open(this.schema.id,lastMigrationPathVersion); //schema version need to be an unsigned long
this.launchMigrationPath = function(dbVersion) {
var transaction = this.dbRequest.transaction;
var clonedMigrations = _.clone(schema.migrations);
this.migrate(transaction, clonedMigrations, dbVersion, {
error: function (event) {
this.error = "Database not up to date. " + dbVersion + " expected was " + lastMigrationPathVersion;
}.bind(this)
});
};
this.dbRequest.onblocked = function(event){
if (!this.nolog) debugLog("connection to database blocked");
}
this.dbRequest.onsuccess = function (e) {
this.db = e.target.result; // Attach the connection ot the queue.
var currentIntDBVersion = (parseInt(this.db.version) || 0); // we need convert beacuse chrome store in integer and ie10 DP4+ in int;
var lastMigrationInt = (parseInt(lastMigrationPathVersion) || 0); // And make sure we compare numbers with numbers.
if (currentIntDBVersion === lastMigrationInt) { //if support new event onupgradeneeded will trigger the ready function
// No migration to perform!
this.ready();
} else if (currentIntDBVersion < lastMigrationInt ) {
// We need to migrate up to the current migration defined in the database
this.launchMigrationPath(currentIntDBVersion);
} else {
// Looks like the IndexedDB is at a higher version than the current driver schema.
this.error = "Database version is greater than current code " + currentIntDBVersion + " expected was " + lastMigrationInt;
}
}.bind(this);
this.dbRequest.onerror = function (e) {
// Failed to open the database
this.error = "Couldn't not connect to the database"
if (!this.nolog) debugLog("Couldn't not connect to the database");
this.onerror();
}.bind(this);
this.dbRequest.onabort = function (e) {
// Failed to open the database
this.error = "Connection to the database aborted"
if (!this.nolog) debugLog("Connection to the database aborted");
this.onerror();
}.bind(this);
this.dbRequest.onupgradeneeded = function(iDBVersionChangeEvent){
this.db =iDBVersionChangeEvent.target.result;
var newVersion = iDBVersionChangeEvent.newVersion;
var oldVersion = iDBVersionChangeEvent.oldVersion;
// Fix Safari 8 and iOS 8 bug
// at the first connection oldVersion is equal to 9223372036854776000
// but the real value is 0
if (oldVersion > 99999999999)
oldVersion = 0;
if (!this.nolog) debugLog("onupgradeneeded = " + oldVersion + " => " + newVersion);
this.launchMigrationPath(oldVersion);
}.bind(this);
}
function debugLog(str) {
if (typeof window !== "undefined" && typeof window.console !== "undefined" && typeof window.console.log !== "undefined") {
window.console.log(str);
}
else if(console.log !== "undefined") {
console.log(str)
}
}
// Driver Prototype
Driver.prototype = {
// Tracks transactions. Mostly for debugging purposes. TO-IMPROVE
_track_transaction: function(transaction) {
this.transactions.push(transaction);
function removeIt() {
var idx = this.transactions.indexOf(transaction);
if (idx !== -1) {this.transactions.splice(idx); }
};
transaction.oncomplete = removeIt.bind(this);
transaction.onabort = removeIt.bind(this);
transaction.onerror = removeIt.bind(this);
},
// Performs all the migrations to reach the right version of the database.
migrate: function (transaction, migrations, version, options) {
transaction.onerror = options.error;
transaction.onabort = options.error;
if (!this.nolog) debugLog("migrate begin version from #" + version);
var that = this;
var migration = migrations.shift();
if (migration) {
if (!version || version < migration.version) {
// We need to apply this migration-
if (typeof migration.before == "undefined") {
migration.before = function (next) {
next();
};
}
if (typeof migration.after == "undefined") {
migration.after = function (next) {
next();
};
}
// First, let's run the before script
if (!this.nolog) debugLog("migrate begin before version #" + migration.version);
migration.before(function () {
if (!this.nolog) debugLog("migrate done before version #" + migration.version);
if (!this.nolog) debugLog("migrate begin migrate version #" + migration.version);
migration.migrate(transaction, function () {
if (!this.nolog) debugLog("migrate done migrate version #" + migration.version);
// Migration successfully appliedn let's go to the next one!
if (!this.nolog) debugLog("migrate begin after version #" + migration.version);
migration.after(function () {
if (!this.nolog) debugLog("migrate done after version #" + migration.version);
if (!this.nolog) debugLog("Migrated to " + migration.version);
//last modification occurred, need finish
if(migrations.length ==0) {
if (!this.nolog) {
debugLog("migrate setting transaction.oncomplete to finish version #" + migration.version);
transaction.oncomplete = function() {
debugLog("migrate done transaction.oncomplete version #" + migration.version);
debugLog("Done migrating");
}
}
}
else
{
if (!this.nolog) debugLog("migrate end from version #" + version + " to " + migration.version);
that.migrate(transaction, migrations, version, options);
}
}.bind(this));
}.bind(this));
}.bind(this));
} else {
// No need to apply this migration
if (!this.nolog) debugLog("Skipping migration " + migration.version);
this.migrate(transaction, migrations, version, options);
}
}
},
// This is the main method, called by the ExecutionQueue when the driver is ready (database open and migration performed)
execute: function (storeName, method, object, options) {
if (!this.nolog) debugLog("execute : " + method + " on " + storeName + " for " + object.id);
switch (method) {
case "create":
this.create(storeName, object, options);
break;
case "read":
if (object.id || object.cid) {
this.read(storeName, object, options); // It's a model
} else {
this.query(storeName, object, options); // It's a collection
}
break;
case "update":
this.update(storeName, object, options); // We may want to check that this is not a collection. TOFIX
break;
case "delete":
if (object.id || object.cid) {
this.delete(storeName, object, options);
} else {
this.clear(storeName, object, options);
}
break;
default:
// Hum what?
}
},
// Writes the json to the storeName in db. It is a create operations, which means it will fail if the key already exists
// options are just success and error callbacks.
create: function (storeName, object, options) {
var writeTransaction = this.db.transaction([storeName], 'readwrite');
//this._track_transaction(writeTransaction);
var store = writeTransaction.objectStore(storeName);
var json = object.toJSON();
var idAttribute = _.result(object, 'idAttribute');
var writeRequest;
if (json[idAttribute] === undefined && !store.autoIncrement) json[idAttribute] = guid();
writeTransaction.onerror = function (e) {
options.error(e);
};
writeTransaction.oncomplete = function (e) {
options.success(json);
};
if (!store.keyPath)
writeRequest = store.add(json, json[idAttribute]);
else
writeRequest = store.add(json);
},
// Writes the json to the storeName in db. It is an update operation, which means it will overwrite the value if the key already exist
// options are just success and error callbacks.
update: function (storeName, object, options) {
var writeTransaction = this.db.transaction([storeName], 'readwrite');
//this._track_transaction(writeTransaction);
var store = writeTransaction.objectStore(storeName);
var json = object.toJSON();
var idAttribute = _.result(object, 'idAttribute');
var writeRequest;
if (!json[idAttribute]) json[idAttribute] = guid();
if (!store.keyPath)
writeRequest = store.put(json, json[idAttribute]);
else
writeRequest = store.put(json);
writeRequest.onerror = function (e) {
options.error(e);
};
writeTransaction.oncomplete = function (e) {
options.success(json);
};
},
// Reads from storeName in db with json.id if it's there of with any json.xxxx as long as xxx is an index in storeName
read: function (storeName, object, options) {
var readTransaction = this.db.transaction([storeName], "readonly");
this._track_transaction(readTransaction);
var store = readTransaction.objectStore(storeName);
var json = object.toJSON();
var idAttribute = _.result(object, 'idAttribute');
var getRequest = null;
if (json[idAttribute]) {
getRequest = store.get(json[idAttribute]);
} else if(options.index) {
var index = store.index(options.index.name);
getRequest = index.get(options.index.value);
} else {
// We need to find which index we have
var cardinality = 0; // try to fit the index with most matches
_.each(store.indexNames, function (key, index) {
index = store.index(key);
if(typeof index.keyPath === 'string' && 1 > cardinality) {
// simple index
if (json[index.keyPath] !== undefined) {
getRequest = index.get(json[index.keyPath]);
cardinality = 1;
}
} else if(typeof index.keyPath === 'object' && index.keyPath.length > cardinality) {
// compound index
var valid = true;
var keyValue = _.map(index.keyPath, function(keyPart) {
valid = valid && json[keyPart] !== undefined;
return json[keyPart];
});
if(valid) {
getRequest = index.get(keyValue);
cardinality = index.keyPath.length;
}
}
});
}
if (getRequest) {
getRequest.onsuccess = function (event) {
if (event.target.result) {
options.success(event.target.result);
} else {
options.error("Not Found");
}
};
getRequest.onerror = function () {
options.error("Not Found"); // We couldn't find the record.
}
} else {
options.error("Not Found"); // We couldn't even look for it, as we don't have enough data.
}
},
// Deletes the json.id key and value in storeName from db.
delete: function (storeName, object, options) {
var deleteTransaction = this.db.transaction([storeName], 'readwrite');
//this._track_transaction(deleteTransaction);
var store = deleteTransaction.objectStore(storeName);
var json = object.toJSON();
var idAttribute = _.result(object, 'idAttribute');
var deleteRequest = store.delete(json[idAttribute]);
deleteTransaction.oncomplete = function (event) {
options.success(null);
};
deleteRequest.onerror = function (event) {
options.error("Not Deleted");
};
},
// Clears all records for storeName from db.
clear: function (storeName, object, options) {
var deleteTransaction = this.db.transaction([storeName], "readwrite");
//this._track_transaction(deleteTransaction);
var store = deleteTransaction.objectStore(storeName);
var deleteRequest = store.clear();
deleteRequest.onsuccess = function (event) {
options.success(null);
};
deleteRequest.onerror = function (event) {
options.error("Not Cleared");
};
},
// Performs a query on storeName in db.
// options may include :
// - conditions : value of an index, or range for an index
// - range : range for the primary key
// - limit : max number of elements to be yielded
// - offset : skipped items.
query: function (storeName, collection, options) {
var elements = [];
var skipped = 0, processed = 0;
var queryTransaction = this.db.transaction([storeName], "readonly");
//this._track_transaction(queryTransaction);
var idAttribute = _.result(collection.model.prototype, 'idAttribute');
var readCursor = null;
var store = queryTransaction.objectStore(storeName);
var index = null,
lower = null,
upper = null,
bounds = null;
if (options.conditions) {
// We have a condition, we need to use it for the cursor
_.each(store.indexNames, function (key) {
if (!readCursor) {
index = store.index(key);
if (options.conditions[index.keyPath] instanceof Array) {
lower = options.conditions[index.keyPath][0] > options.conditions[index.keyPath][1] ? options.conditions[index.keyPath][1] : options.conditions[index.keyPath][0];
upper = options.conditions[index.keyPath][0] > options.conditions[index.keyPath][1] ? options.conditions[index.keyPath][0] : options.conditions[index.keyPath][1];
bounds = IDBKeyRange.bound(lower, upper, true, true);
if (options.conditions[index.keyPath][0] > options.conditions[index.keyPath][1]) {
// Looks like we want the DESC order
readCursor = index.openCursor(bounds, window.IDBCursor.PREV || "prev");
} else {
// We want ASC order
readCursor = index.openCursor(bounds, window.IDBCursor.NEXT || "next");
}
} else if (typeof options.conditions[index.keyPath] === 'object' && ('$gt' in options.conditions[index.keyPath] || '$gte' in options.conditions[index.keyPath])) {
if('$gt' in options.conditions[index.keyPath])
bounds = IDBKeyRange.lowerBound(options.conditions[index.keyPath]['$gt'], true);
else
bounds = IDBKeyRange.lowerBound(options.conditions[index.keyPath]['$gte']);
readCursor = index.openCursor(bounds, window.IDBCursor.NEXT || "next");
} else if (typeof options.conditions[index.keyPath] === 'object' && ('$lt' in options.conditions[index.keyPath] || '$lte' in options.conditions[index.keyPath])) {
if('$lt' in options.conditions[index.keyPath])
bounds = IDBKeyRange.upperBound(options.conditions[index.keyPath]['$lt'], true);
else
bounds = IDBKeyRange.upperBound(options.conditions[index.keyPath]['$lte']);
readCursor = index.openCursor(bounds, window.IDBCursor.NEXT || "next");
} else if (options.conditions[index.keyPath] != undefined) {
bounds = IDBKeyRange.only(options.conditions[index.keyPath]);
readCursor = index.openCursor(bounds);
}
}
});
} else if (options.index) {
index = store.index(options.index.name);
var excludeLower = !!options.index.excludeLower;
var excludeUpper = !!options.index.excludeUpper;
if (index) {
if (options.index.lower && options.index.upper) {
bounds = IDBKeyRange.bound(options.index.lower, options.index.upper, excludeLower, excludeUpper);
} else if (options.index.lower) {
bounds = IDBKeyRange.lowerBound(options.index.lower, excludeLower);
} else if (options.index.upper) {
bounds = IDBKeyRange.upperBound(options.index.upper, excludeUpper);
} else if (options.index.only) {
bounds = IDBKeyRange.only(options.index.only);
}
if (typeof options.index.order === 'string' && options.index.order.toLowerCase() === 'desc') {
readCursor = index.openCursor(bounds, window.IDBCursor.PREV || "prev");
} else {
readCursor = index.openCursor(bounds, window.IDBCursor.NEXT || "next");
}
}
} else {
// No conditions, use the index
if (options.range) {
lower = options.range[0] > options.range[1] ? options.range[1] : options.range[0];
upper = options.range[0] > options.range[1] ? options.range[0] : options.range[1];
bounds = IDBKeyRange.bound(lower, upper);
if (options.range[0] > options.range[1]) {
readCursor = store.openCursor(bounds, window.IDBCursor.PREV || "prev");
} else {
readCursor = store.openCursor(bounds, window.IDBCursor.NEXT || "next");
}
} else {
readCursor = store.openCursor();
}
}
if (typeof (readCursor) == "undefined" || !readCursor) {
options.error("No Cursor");
} else {
readCursor.onerror = function(e){
options.error("readCursor error", e);
};
// Setup a handler for the cursors `success` event:
readCursor.onsuccess = function (e) {
var cursor = e.target.result;
if (!cursor) {
if (options.addIndividually || options.clear) {
// nothing!
// We need to indicate that we're done. But, how?
collection.trigger("reset");
} else {
options.success(elements); // We're done. No more elements.
}
}
else {
// Cursor is not over yet.
if (options.limit && processed >= options.limit) {
// Yet, we have processed enough elements. So, let's just skip.
if (bounds) {
if (options.conditions && options.conditions[index.keyPath]) {
cursor.continue(options.conditions[index.keyPath][1] + 1); /* We need to 'terminate' the cursor cleany, by moving to the end */
} else if (options.index && (options.index.upper || options.index.lower)) {
if (typeof options.index.order === 'string' && options.index.order.toLowerCase() === 'desc') {
cursor.continue(options.index.lower);
} else {
cursor.continue(options.index.upper);
}
}
} else {
cursor.continue(); /* We need to 'terminate' the cursor cleany, by moving to the end */
}
}
else if (options.offset && options.offset > skipped) {
skipped++;
cursor.continue(); /* We need to Moving the cursor forward */
} else {
// This time, it looks like it's good!
if (options.addIndividually) {
collection.add(cursor.value);
} else if (options.clear) {
var deleteRequest = store.delete(cursor.value[idAttribute]);
deleteRequest.onsuccess = function (event) {
elements.push(cursor.value);
};
deleteRequest.onerror = function (event) {
elements.push(cursor.value);
};
} else {
elements.push(cursor.value);
}
processed++;
cursor.continue();
}
}
};
}
},
close :function(){
if(this.db){
this.db.close();
}
}
};
// ExecutionQueue object
// The execution queue is an abstraction to buffer up requests to the database.
// It holds a "driver". When the driver is ready, it just fires up the queue and executes in sync.
function ExecutionQueue(schema,next,nolog) {
this.driver = new Driver(schema, this.ready.bind(this), nolog, this.error.bind(this));
this.started = false;
this.failed = false;
this.stack = [];
this.version = _.last(schema.migrations).version;
this.next = next;
}
// ExecutionQueue Prototype
ExecutionQueue.prototype = {
// Called when the driver is ready
// It just loops over the elements in the queue and executes them.
ready: function () {
this.started = true;
_.each(this.stack, function (message) {
this.execute(message);
}.bind(this));
this.stack = []; // fix memory leak
this.next();
},
error: function() {
this.failed = true;
_.each(this.stack, function (message) {
this.execute(message);
}.bind(this));
this.stack = [];
this.next();
},
// Executes a given command on the driver. If not started, just stacks up one more element.
execute: function (message) {
if (this.started) {
try {
this.driver.execute(message[2].storeName || message[1].storeName, message[0], message[1], message[2]); // Upon messages, we execute the query
} catch (e) {
if (e.name === 'InvalidStateError') {
var f = window.onInvalidStateError;
if (f) f(e);
}
throw e;
}
} else if (this.failed) {
message[2].error();
} else {
this.stack.push(message);
}
},
close : function(){
this.driver.close();
}
};
// Method used by Backbone for sync of data with data store. It was initially designed to work with "server side" APIs, This wrapper makes
// it work with the local indexedDB stuff. It uses the schema attribute provided by the object.
// The wrapper keeps an active Executuon Queue for each "schema", and executes querues agains it, based on the object type (collection or
// single model), but also the method... etc.
// Keeps track of the connections
var Databases = {};
function sync(method, object, options) {
if(method == "closeall"){
_.each(Databases,function(database){
database.close();
});
// Clean up active databases object.
Databases = {};
return Backbone.$.Deferred().resolve();
}
// If a model or a collection does not define a database, fall back on ajaxSync
if (!object || !_.isObject(object.database)) {
return Backbone.ajaxSync(method, object, options);
}
var schema = object.database;
if (Databases[schema.id]) {
if(Databases[schema.id].version != _.last(schema.migrations).version){
Databases[schema.id].close();
delete Databases[schema.id];
}
}
var promise;
if (typeof Backbone.$ === 'undefined' || typeof Backbone.$.Deferred === 'undefined') {
var noop = function() {};
var resolve = noop;
var reject = noop;
} else {
var dfd = Backbone.$.Deferred();
var resolve = dfd.resolve;
var reject = dfd.reject;
promise = dfd.promise();
}
var success = options.success;
options.success = function(resp) {
if (success) success(resp);
resolve();
object.trigger('sync', object, resp, options);
};
var error = options.error;
options.error = function(resp) {
if (error) error(resp);
reject();
object.trigger('error', object, resp, options);
};
var next = function(){
Databases[schema.id].execute([method, object, options]);
};
if (!Databases[schema.id]) {
Databases[schema.id] = new ExecutionQueue(schema,next,schema.nolog);
} else {
next();
}
return promise;
};
Backbone.ajaxSync = Backbone.sync;
Backbone.sync = sync;
return { sync: sync, debugLog: debugLog};
}));

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,25 @@
<!DOCTYPE html>
<html class='no-js' lang='en'>
<head>
<meta charset='utf-8'>
<meta content='width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0' name='viewport'>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Session</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href='/images/session/session_icon_128.png' rel='shortcut icon'>
<link href="/stylesheets/manifest.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="js/chromium.js"></script>
</head>
<body id="signal-container" class='signal index'>
<div class='app-loading-screen'>
<div class="content session-full-logo">
<img src="images/session/brand.svg" class="session-brand-logo" />
<img src="images/session/session-text.svg" class="session-text-logo" />
</div>
</div>
<script type="text/javascript" src="js/index.js"></script>
</body>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<meta
content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"
name="viewport"
/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Session</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="/images/session/session_icon_128.png" rel="shortcut icon" />
<link href="/stylesheets/manifest.css" rel="stylesheet" type="text/css" />
</head>
<body id="signal-container" class="signal index">
<div class="app-loading-screen">
<div class="content session-full-logo">
<img src="images/session/brand.svg" class="session-brand-logo" />
<img src="images/session/session-text.svg" class="session-text-logo" />
</div>
</div>
<script type="text/javascript" src="js/index.js"></script>
</body>
</html>

View File

@ -66,11 +66,6 @@
window.getGlobalOnlineStatus = () => window.globalOnlineStatus;
const { Views } = window.Signal;
// Implicitly used in `indexeddb-backbonejs-adapter`:
// https://github.com/signalapp/Signal-Desktop/blob/4033a9f8137e62ed286170ed5d4941982b1d3a64/components/indexeddb-backbonejs-adapter/backbone-indexeddb.js#L569
window.onInvalidStateError = error =>
window.log.error(error && error.stack ? error.stack : error);
window.log.info('background page reloaded');
window.log.info('environment:', window.getEnvironment());
const restartReason = localStorage.getItem('restart-reason');
@ -149,15 +144,6 @@
storage.put('spell-check', value);
},
addDarkOverlay: () => {
if ($('.dark-overlay').length) {
return;
}
$(document.body).prepend('<div class="dark-overlay"></div>');
$('.dark-overlay').on('click', () => $('.dark-overlay').remove());
},
removeDarkOverlay: () => $('.dark-overlay').remove(),
shutdown: async () => {
// Stop background processing
window.libsession.Utils.AttachmentDownloads.stop();

View File

@ -1,15 +0,0 @@
/* global extension: false */
// eslint-disable-next-line func-names
(function() {
'use strict';
// Browser specific functions for Chrom*
window.extension = window.extension || {};
extension.windows = {
onClosed(callback) {
window.addEventListener('beforeunload', callback);
},
};
})();

View File

@ -35584,315 +35584,7 @@ var MEMFS = {
},
};
var IDBFS = {
dbs: {},
indexedDB: function() {
if (typeof indexedDB !== 'undefined') return indexedDB;
var ret = null;
if (typeof window === 'object')
ret =
window.indexedDB ||
window.mozIndexedDB ||
window.webkitIndexedDB ||
window.msIndexedDB;
assert(ret, 'IDBFS used, but indexedDB not supported');
return ret;
},
DB_VERSION: 21,
DB_STORE_NAME: 'FILE_DATA',
mount: function(mount) {
// reuse all of the core MEMFS functionality
return MEMFS.mount.apply(null, arguments);
},
syncfs: function(mount, populate, callback) {
IDBFS.getLocalSet(mount, function(err, local) {
if (err) return callback(err);
IDBFS.getRemoteSet(mount, function(err, remote) {
if (err) return callback(err);
var src = populate ? remote : local;
var dst = populate ? local : remote;
IDBFS.reconcile(src, dst, callback);
});
});
},
getDB: function(name, callback) {
// check the cache first
var db = IDBFS.dbs[name];
if (db) {
return callback(null, db);
}
var req;
try {
req = IDBFS.indexedDB().open(name, IDBFS.DB_VERSION);
} catch (e) {
return callback(e);
}
req.onupgradeneeded = function(e) {
var db = e.target.result;
var transaction = e.target.transaction;
var fileStore;
if (db.objectStoreNames.contains(IDBFS.DB_STORE_NAME)) {
fileStore = transaction.objectStore(IDBFS.DB_STORE_NAME);
} else {
fileStore = db.createObjectStore(IDBFS.DB_STORE_NAME);
}
fileStore.createIndex('timestamp', 'timestamp', { unique: false });
};
req.onsuccess = function() {
db = req.result;
// add to the cache
IDBFS.dbs[name] = db;
callback(null, db);
};
req.onerror = function() {
callback(this.error);
};
},
getLocalSet: function(mount, callback) {
var entries = {};
function isRealDir(p) {
return p !== '.' && p !== '..';
}
function toAbsolute(root) {
return function(p) {
return PATH.join2(root, p);
};
}
var check = FS.readdir(mount.mountpoint)
.filter(isRealDir)
.map(toAbsolute(mount.mountpoint));
while (check.length) {
var path = check.pop();
var stat;
try {
stat = FS.stat(path);
} catch (e) {
return callback(e);
}
if (FS.isDir(stat.mode)) {
check.push.apply(
check,
FS.readdir(path)
.filter(isRealDir)
.map(toAbsolute(path))
);
}
entries[path] = { timestamp: stat.mtime };
}
return callback(null, { type: 'local', entries: entries });
},
getRemoteSet: function(mount, callback) {
var entries = {};
IDBFS.getDB(mount.mountpoint, function(err, db) {
if (err) return callback(err);
var transaction = db.transaction([IDBFS.DB_STORE_NAME], 'readonly');
transaction.onerror = function() {
callback(this.error);
};
var store = transaction.objectStore(IDBFS.DB_STORE_NAME);
var index = store.index('timestamp');
index.openKeyCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (!cursor) {
return callback(null, { type: 'remote', db: db, entries: entries });
}
entries[cursor.primaryKey] = { timestamp: cursor.key };
cursor.continue();
};
});
},
loadLocalEntry: function(path, callback) {
var stat, node;
try {
var lookup = FS.lookupPath(path);
node = lookup.node;
stat = FS.stat(path);
} catch (e) {
return callback(e);
}
if (FS.isDir(stat.mode)) {
return callback(null, { timestamp: stat.mtime, mode: stat.mode });
} else if (FS.isFile(stat.mode)) {
// Performance consideration: storing a normal JavaScript array to a IndexedDB is much slower than storing a typed array.
// Therefore always convert the file contents to a typed array first before writing the data to IndexedDB.
node.contents = MEMFS.getFileDataAsTypedArray(node);
return callback(null, {
timestamp: stat.mtime,
mode: stat.mode,
contents: node.contents,
});
} else {
return callback(new Error('node type not supported'));
}
},
storeLocalEntry: function(path, entry, callback) {
try {
if (FS.isDir(entry.mode)) {
FS.mkdir(path, entry.mode);
} else if (FS.isFile(entry.mode)) {
FS.writeFile(path, entry.contents, {
encoding: 'binary',
canOwn: true,
});
} else {
return callback(new Error('node type not supported'));
}
FS.chmod(path, entry.mode);
FS.utime(path, entry.timestamp, entry.timestamp);
} catch (e) {
return callback(e);
}
callback(null);
},
removeLocalEntry: function(path, callback) {
try {
var lookup = FS.lookupPath(path);
var stat = FS.stat(path);
if (FS.isDir(stat.mode)) {
FS.rmdir(path);
} else if (FS.isFile(stat.mode)) {
FS.unlink(path);
}
} catch (e) {
return callback(e);
}
callback(null);
},
loadRemoteEntry: function(store, path, callback) {
var req = store.get(path);
req.onsuccess = function(event) {
callback(null, event.target.result);
};
req.onerror = function() {
callback(this.error);
};
},
storeRemoteEntry: function(store, path, entry, callback) {
var req = store.put(entry, path);
req.onsuccess = function() {
callback(null);
};
req.onerror = function() {
callback(this.error);
};
},
removeRemoteEntry: function(store, path, callback) {
var req = store.delete(path);
req.onsuccess = function() {
callback(null);
};
req.onerror = function() {
callback(this.error);
};
},
reconcile: function(src, dst, callback) {
var total = 0;
var create = [];
Object.keys(src.entries).forEach(function(key) {
var e = src.entries[key];
var e2 = dst.entries[key];
if (!e2 || e.timestamp > e2.timestamp) {
create.push(key);
total++;
}
});
var remove = [];
Object.keys(dst.entries).forEach(function(key) {
var e = dst.entries[key];
var e2 = src.entries[key];
if (!e2) {
remove.push(key);
total++;
}
});
if (!total) {
return callback(null);
}
var errored = false;
var completed = 0;
var db = src.type === 'remote' ? src.db : dst.db;
var transaction = db.transaction([IDBFS.DB_STORE_NAME], 'readwrite');
var store = transaction.objectStore(IDBFS.DB_STORE_NAME);
function done(err) {
if (err) {
if (!done.errored) {
done.errored = true;
return callback(err);
}
return;
}
if (++completed >= total) {
return callback(null);
}
}
transaction.onerror = function() {
done(this.error);
};
// sort paths in ascending order so directory entries are created
// before the files inside them
create.sort().forEach(function(path) {
if (dst.type === 'local') {
IDBFS.loadRemoteEntry(store, path, function(err, entry) {
if (err) return done(err);
IDBFS.storeLocalEntry(path, entry, done);
});
} else {
IDBFS.loadLocalEntry(path, function(err, entry) {
if (err) return done(err);
IDBFS.storeRemoteEntry(store, path, entry, done);
});
}
});
// sort paths in descending order so files are deleted before their
// parent directories
remove
.sort()
.reverse()
.forEach(function(path) {
if (dst.type === 'local') {
IDBFS.removeLocalEntry(path, done);
} else {
IDBFS.removeRemoteEntry(store, path, done);
}
});
},
};
var NODEFS = {
isWindows: false,
@ -38010,111 +37702,6 @@ var FS = {
processData(url);
}
},
indexedDB: function() {
return (
window.indexedDB ||
window.mozIndexedDB ||
window.webkitIndexedDB ||
window.msIndexedDB
);
},
DB_NAME: function() {
return 'EM_FS_' + window.location.pathname;
},
DB_VERSION: 20,
DB_STORE_NAME: 'FILE_DATA',
saveFilesToDB: function(paths, onload, onerror) {
onload = onload || function() {};
onerror = onerror || function() {};
var indexedDB = FS.indexedDB();
try {
var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION);
} catch (e) {
return onerror(e);
}
openRequest.onupgradeneeded = function openRequest_onupgradeneeded() {
console.log('creating db');
var db = openRequest.result;
db.createObjectStore(FS.DB_STORE_NAME);
};
openRequest.onsuccess = function openRequest_onsuccess() {
var db = openRequest.result;
var transaction = db.transaction([FS.DB_STORE_NAME], 'readwrite');
var files = transaction.objectStore(FS.DB_STORE_NAME);
var ok = 0,
fail = 0,
total = paths.length;
function finish() {
if (fail == 0) onload();
else onerror();
}
paths.forEach(function(path) {
var putRequest = files.put(FS.analyzePath(path).object.contents, path);
putRequest.onsuccess = function putRequest_onsuccess() {
ok++;
if (ok + fail == total) finish();
};
putRequest.onerror = function putRequest_onerror() {
fail++;
if (ok + fail == total) finish();
};
});
transaction.onerror = onerror;
};
openRequest.onerror = onerror;
},
loadFilesFromDB: function(paths, onload, onerror) {
onload = onload || function() {};
onerror = onerror || function() {};
var indexedDB = FS.indexedDB();
try {
var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION);
} catch (e) {
return onerror(e);
}
openRequest.onupgradeneeded = onerror; // no database to load from
openRequest.onsuccess = function openRequest_onsuccess() {
var db = openRequest.result;
try {
var transaction = db.transaction([FS.DB_STORE_NAME], 'readonly');
} catch (e) {
onerror(e);
return;
}
var files = transaction.objectStore(FS.DB_STORE_NAME);
var ok = 0,
fail = 0,
total = paths.length;
function finish() {
if (fail == 0) onload();
else onerror();
}
paths.forEach(function(path) {
var getRequest = files.get(path);
getRequest.onsuccess = function getRequest_onsuccess() {
if (FS.analyzePath(path).exists) {
FS.unlink(path);
}
FS.createDataFile(
PATH.dirname(path),
PATH.basename(path),
getRequest.result,
true,
true,
true
);
ok++;
if (ok + fail == total) finish();
};
getRequest.onerror = function getRequest_onerror() {
fail++;
if (ok + fail == total) finish();
};
});
transaction.onerror = onerror;
};
openRequest.onerror = onerror;
},
};
var PATH = {
splitPath: function(filename) {

View File

@ -1,122 +0,0 @@
/* global _: false */
/* global Backbone: false */
/* global Whisper: false */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
window.Whisper.Database = window.Whisper.Database || {};
window.Whisper.Database.id = window.Whisper.Database.id || 'loki-messenger';
window.Whisper.Database.nolog = true;
Whisper.Database.handleDOMException = (prefix, error, reject) => {
window.log.error(
`${prefix}:`,
error && error.name,
error && error.message,
error && error.code
);
reject(error || new Error(prefix));
};
function clearStores(db, names) {
return new Promise((resolve, reject) => {
const storeNames = names || db.objectStoreNames;
window.log.info('Clearing these indexeddb stores:', storeNames);
const transaction = db.transaction(storeNames, 'readwrite');
let finished = false;
const finish = via => {
window.log.info('clearing all stores done via', via);
if (finished) {
resolve();
}
finished = true;
};
transaction.oncomplete = finish.bind(null, 'transaction complete');
transaction.onerror = () => {
Whisper.Database.handleDOMException(
'clearStores transaction error',
transaction.error,
reject
);
};
let count = 0;
// can't use built-in .forEach because db.objectStoreNames is not a plain array
_.forEach(storeNames, storeName => {
const store = transaction.objectStore(storeName);
const request = store.clear();
request.onsuccess = () => {
count += 1;
window.log.info('Done clearing store', storeName);
if (count >= storeNames.length) {
window.log.info('Done clearing indexeddb stores');
finish('clears complete');
}
};
request.onerror = () => {
Whisper.Database.handleDOMException('clearStores request error', request.error, reject);
};
});
});
}
Whisper.Database.open = () => {
const { migrations } = Whisper.Database;
const { version } = migrations[migrations.length - 1];
const DBOpenRequest = window.indexedDB.open(Whisper.Database.id, version);
return new Promise((resolve, reject) => {
// these two event handlers act on the IDBDatabase object,
// when the database is opened successfully, or not
DBOpenRequest.onerror = reject;
DBOpenRequest.onsuccess = () => resolve(DBOpenRequest.result);
// This event handles the event whereby a new version of
// the database needs to be created Either one has not
// been created before, or a new version number has been
// submitted via the window.indexedDB.open line above
DBOpenRequest.onupgradeneeded = reject;
});
};
Whisper.Database.clear = async () => {
const db = await Whisper.Database.open();
await clearStores(db);
db.close();
};
Whisper.Database.clearStores = async storeNames => {
const db = await Whisper.Database.open();
await clearStores(db, storeNames);
db.close();
};
Whisper.Database.close = () => window.wrapDeferred(Backbone.sync('closeall'));
Whisper.Database.drop = () =>
new Promise((resolve, reject) => {
const request = window.indexedDB.deleteDatabase(Whisper.Database.id);
request.onblocked = () => {
reject(new Error('Error deleting database: Blocked.'));
};
request.onupgradeneeded = () => {
reject(new Error('Error deleting database: Upgrade needed.'));
};
request.onerror = () => {
reject(new Error('Error deleting database.'));
};
request.onsuccess = resolve;
});
})();

View File

@ -1,89 +0,0 @@
/* global Backbone, Whisper */
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
const Item = Backbone.Model.extend({
database: Whisper.Database,
storeName: 'items',
});
const ItemCollection = Backbone.Collection.extend({
model: Item,
storeName: 'items',
database: Whisper.Database,
});
let ready = false;
const items = new ItemCollection();
items.on('reset', () => {
ready = true;
});
window.legacyStorage = {
/** ***************************
*** Base Storage Routines ***
**************************** */
put(key, value) {
if (value === undefined) {
throw new Error('Tried to store undefined');
}
if (!ready) {
window.log.warn('Called storage.put before storage is ready. key:', key);
}
const item = items.add({ id: key, value }, { merge: true });
return new Promise((resolve, reject) => {
item.save().then(resolve, reject);
});
},
get(key, defaultValue) {
const item = items.get(`${key}`);
if (!item) {
return defaultValue;
}
return item.get('value');
},
remove(key) {
const item = items.get(`${key}`);
if (item) {
items.remove(item);
return new Promise((resolve, reject) => {
item.destroy().then(resolve, reject);
});
}
return Promise.resolve();
},
onready(callback) {
if (ready) {
callback();
} else {
items.on('reset', callback);
}
},
fetch() {
return new Promise((resolve, reject) => {
items
.fetch({ reset: true })
.fail(() =>
reject(
new Error(
'Failed to fetch from storage.' +
' This may be due to an unexpected database version.'
)
)
)
.always(resolve);
});
},
reset() {
items.reset();
},
};
})();

View File

View File

@ -3,13 +3,13 @@
/* eslint strict: ['error', 'never'] */
/* eslint-disable no-console */
const electron = require('electron');
const { ipcRenderer } = require('electron');
const _ = require('lodash');
const debuglogs = require('./modules/debuglogs');
const Privacy = require('./modules/privacy');
const ipc = electron.ipcRenderer;
const ipc = ipcRenderer;
// Default Bunyan levels: https://github.com/trentm/node-bunyan#levels
// To make it easier to visually scan logs, we make all levels the same length

View File

@ -1,65 +0,0 @@
/* global indexedDB */
// Module for interacting with IndexedDB without Backbone IndexedDB adapter
// and using promises. Revisit use of `idb` dependency as it might cover
// this functionality.
const { isObject, isNumber } = require('lodash');
exports.open = (name, version, { onUpgradeNeeded } = {}) => {
const request = indexedDB.open(name, version);
return new Promise((resolve, reject) => {
request.onblocked = () => reject(new Error('Database blocked'));
request.onupgradeneeded = event => {
const hasRequestedSpecificVersion = isNumber(version);
if (!hasRequestedSpecificVersion) {
return;
}
const { newVersion, oldVersion } = event;
if (onUpgradeNeeded) {
const { transaction } = event.target;
onUpgradeNeeded({ oldVersion, transaction });
return;
}
reject(
new Error(`Database upgrade required: oldVersion: ${oldVersion}, newVersion: ${newVersion}`)
);
};
request.onerror = event => reject(event.target.error);
request.onsuccess = event => {
const connection = event.target.result;
resolve(connection);
};
});
};
exports.completeTransaction = transaction =>
new Promise((resolve, reject) => {
transaction.addEventListener('abort', event => reject(event.target.error));
transaction.addEventListener('error', event => reject(event.target.error));
transaction.addEventListener('complete', () => resolve());
});
exports.getVersion = async name => {
const connection = await exports.open(name);
const { version } = connection;
connection.close();
return version;
};
exports.getCount = async ({ store } = {}) => {
if (!isObject(store)) {
throw new TypeError("'store' is required");
}
const request = store.count();
return new Promise((resolve, reject) => {
request.onerror = event => reject(event.target.error);
request.onsuccess = event => resolve(event.target.result);
});
};

View File

@ -1,24 +0,0 @@
/* eslint-disable more/no-then */
class JobQueue {
constructor() {
this.pending = Promise.resolve();
}
add(job) {
const previous = this.pending || Promise.resolve();
this.pending = previous.then(job, job);
const current = this.pending;
current.then(() => {
if (this.pending === current) {
delete this.pending;
}
});
return current;
}
}
module.exports = {
JobQueue,
};

View File

@ -2,7 +2,6 @@
const Crypto = require('./crypto');
const Data = require('../../ts/data/data');
const Database = require('./database');
const Emoji = require('../../ts/util/emoji');
const Notifications = require('../../ts/notifications');
const OS = require('../../ts/OS');
@ -143,7 +142,6 @@ exports.setup = (options = {}) => {
Components,
Crypto,
Data,
Database,
Emoji,
LinkPreviews,
Migrations,

View File

@ -1,3 +0,0 @@
/* global setTimeout */
exports.sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

View File

@ -48,7 +48,6 @@
}
},
openStandalone() {
window.addSetupMenuItems();
this.resetViews();
this.standaloneView = new Whisper.SessionRegistrationView();
this.openView(this.standaloneView);

View File

@ -1,82 +0,0 @@
/* global window, Event, textsecure */
/*
* Implements EventTarget
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
*/
// eslint-disable-next-line func-names
(function() {
window.textsecure = window.textsecure || {};
function EventTarget() {}
EventTarget.prototype = {
constructor: EventTarget,
dispatchEvent(ev) {
if (!(ev instanceof Event)) {
throw new Error('Expects an event');
}
if (this.listeners === null || typeof this.listeners !== 'object') {
this.listeners = {};
}
const listeners = this.listeners[ev.type];
const results = [];
if (typeof listeners === 'object') {
for (let i = 0, max = listeners.length; i < max; i += 1) {
const listener = listeners[i];
if (typeof listener === 'function') {
results.push(listener.call(null, ev));
}
}
}
return results;
},
addEventListener(eventName, callback) {
if (typeof eventName !== 'string') {
throw new Error('First argument expects a string');
}
if (typeof callback !== 'function') {
throw new Error('Second argument expects a function');
}
if (this.listeners === null || typeof this.listeners !== 'object') {
this.listeners = {};
}
let listeners = this.listeners[eventName];
if (typeof listeners !== 'object') {
listeners = [];
}
listeners.push(callback);
this.listeners[eventName] = listeners;
},
removeEventListener(eventName, callback) {
if (typeof eventName !== 'string') {
throw new Error('First argument expects a string');
}
if (typeof callback !== 'function') {
throw new Error('Second argument expects a function');
}
if (this.listeners === null || typeof this.listeners !== 'object') {
this.listeners = {};
}
const listeners = this.listeners[eventName];
if (typeof listeners === 'object') {
for (let i = 0; i < listeners.length; i += 1) {
if (listeners[i] === callback) {
listeners.splice(i, 1);
return;
}
}
}
this.listeners[eventName] = listeners;
},
extend(obj) {
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const prop in obj) {
this[prop] = obj[prop];
}
return this;
},
};
textsecure.EventTarget = EventTarget;
})();

View File

@ -2038,263 +2038,6 @@ var libsignal
}
};
var IDBFS = {
dbs: {}, indexedDB: function () {
if (typeof indexedDB !== 'undefined') return indexedDB;
var ret = null;
if (typeof window === 'object') ret = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
assert(ret, 'IDBFS used, but indexedDB not supported');
return ret;
}, DB_VERSION: 21, DB_STORE_NAME: "FILE_DATA", mount: function (mount) {
// reuse all of the core MEMFS functionality
return MEMFS.mount.apply(null, arguments);
}, syncfs: function (mount, populate, callback) {
IDBFS.getLocalSet(mount, function (err, local) {
if (err) return callback(err);
IDBFS.getRemoteSet(mount, function (err, remote) {
if (err) return callback(err);
var src = populate ? remote : local;
var dst = populate ? local : remote;
IDBFS.reconcile(src, dst, callback);
});
});
}, getDB: function (name, callback) {
// check the cache first
var db = IDBFS.dbs[name];
if (db) {
return callback(null, db);
}
var req;
try {
req = IDBFS.indexedDB().open(name, IDBFS.DB_VERSION);
} catch (e) {
return callback(e);
}
req.onupgradeneeded = function (e) {
var db = e.target.result;
var transaction = e.target.transaction;
var fileStore;
if (db.objectStoreNames.contains(IDBFS.DB_STORE_NAME)) {
fileStore = transaction.objectStore(IDBFS.DB_STORE_NAME);
} else {
fileStore = db.createObjectStore(IDBFS.DB_STORE_NAME);
}
fileStore.createIndex('timestamp', 'timestamp', { unique: false });
};
req.onsuccess = function () {
db = req.result;
// add to the cache
IDBFS.dbs[name] = db;
callback(null, db);
};
req.onerror = function () {
callback(this.error);
};
}, getLocalSet: function (mount, callback) {
var entries = {};
function isRealDir(p) {
return p !== '.' && p !== '..';
};
function toAbsolute(root) {
return function (p) {
return PATH.join2(root, p);
}
};
var check = FS.readdir(mount.mountpoint).filter(isRealDir).map(toAbsolute(mount.mountpoint));
while (check.length) {
var path = check.pop();
var stat;
try {
stat = FS.stat(path);
} catch (e) {
return callback(e);
}
if (FS.isDir(stat.mode)) {
check.push.apply(check, FS.readdir(path).filter(isRealDir).map(toAbsolute(path)));
}
entries[path] = { timestamp: stat.mtime };
}
return callback(null, { type: 'local', entries: entries });
}, getRemoteSet: function (mount, callback) {
var entries = {};
IDBFS.getDB(mount.mountpoint, function (err, db) {
if (err) return callback(err);
var transaction = db.transaction([IDBFS.DB_STORE_NAME], 'readonly');
transaction.onerror = function () { callback(this.error); };
var store = transaction.objectStore(IDBFS.DB_STORE_NAME);
var index = store.index('timestamp');
index.openKeyCursor().onsuccess = function (event) {
var cursor = event.target.result;
if (!cursor) {
return callback(null, { type: 'remote', db: db, entries: entries });
}
entries[cursor.primaryKey] = { timestamp: cursor.key };
cursor.continue();
};
});
}, loadLocalEntry: function (path, callback) {
var stat, node;
try {
var lookup = FS.lookupPath(path);
node = lookup.node;
stat = FS.stat(path);
} catch (e) {
return callback(e);
}
if (FS.isDir(stat.mode)) {
return callback(null, { timestamp: stat.mtime, mode: stat.mode });
} else if (FS.isFile(stat.mode)) {
// Performance consideration: storing a normal JavaScript array to a IndexedDB is much slower than storing a typed array.
// Therefore always convert the file contents to a typed array first before writing the data to IndexedDB.
node.contents = MEMFS.getFileDataAsTypedArray(node);
return callback(null, { timestamp: stat.mtime, mode: stat.mode, contents: node.contents });
} else {
return callback(new Error('node type not supported'));
}
}, storeLocalEntry: function (path, entry, callback) {
try {
if (FS.isDir(entry.mode)) {
FS.mkdir(path, entry.mode);
} else if (FS.isFile(entry.mode)) {
FS.writeFile(path, entry.contents, { encoding: 'binary', canOwn: true });
} else {
return callback(new Error('node type not supported'));
}
FS.chmod(path, entry.mode);
FS.utime(path, entry.timestamp, entry.timestamp);
} catch (e) {
return callback(e);
}
callback(null);
}, removeLocalEntry: function (path, callback) {
try {
var lookup = FS.lookupPath(path);
var stat = FS.stat(path);
if (FS.isDir(stat.mode)) {
FS.rmdir(path);
} else if (FS.isFile(stat.mode)) {
FS.unlink(path);
}
} catch (e) {
return callback(e);
}
callback(null);
}, loadRemoteEntry: function (store, path, callback) {
var req = store.get(path);
req.onsuccess = function (event) { callback(null, event.target.result); };
req.onerror = function () { callback(this.error); };
}, storeRemoteEntry: function (store, path, entry, callback) {
var req = store.put(entry, path);
req.onsuccess = function () { callback(null); };
req.onerror = function () { callback(this.error); };
}, removeRemoteEntry: function (store, path, callback) {
var req = store.delete(path);
req.onsuccess = function () { callback(null); };
req.onerror = function () { callback(this.error); };
}, reconcile: function (src, dst, callback) {
var total = 0;
var create = [];
Object.keys(src.entries).forEach(function (key) {
var e = src.entries[key];
var e2 = dst.entries[key];
if (!e2 || e.timestamp > e2.timestamp) {
create.push(key);
total++;
}
});
var remove = [];
Object.keys(dst.entries).forEach(function (key) {
var e = dst.entries[key];
var e2 = src.entries[key];
if (!e2) {
remove.push(key);
total++;
}
});
if (!total) {
return callback(null);
}
var errored = false;
var completed = 0;
var db = src.type === 'remote' ? src.db : dst.db;
var transaction = db.transaction([IDBFS.DB_STORE_NAME], 'readwrite');
var store = transaction.objectStore(IDBFS.DB_STORE_NAME);
function done(err) {
if (err) {
if (!done.errored) {
done.errored = true;
return callback(err);
}
return;
}
if (++completed >= total) {
return callback(null);
}
};
transaction.onerror = function () { done(this.error); };
// sort paths in ascending order so directory entries are created
// before the files inside them
create.sort().forEach(function (path) {
if (dst.type === 'local') {
IDBFS.loadRemoteEntry(store, path, function (err, entry) {
if (err) return done(err);
IDBFS.storeLocalEntry(path, entry, done);
});
} else {
IDBFS.loadLocalEntry(path, function (err, entry) {
if (err) return done(err);
IDBFS.storeRemoteEntry(store, path, entry, done);
});
}
});
// sort paths in descending order so files are deleted before their
// parent directories
remove.sort().reverse().forEach(function (path) {
if (dst.type === 'local') {
IDBFS.removeLocalEntry(path, done);
} else {
IDBFS.removeRemoteEntry(store, path, done);
}
});
}
};
var NODEFS = {
isWindows: false, staticInit: function () {
NODEFS.isWindows = !!process.platform.match(/^win/);
@ -4049,79 +3792,7 @@ var libsignal
} else {
processData(url);
}
}, indexedDB: function () {
return window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
}, DB_NAME: function () {
return 'EM_FS_' + window.location.pathname;
}, DB_VERSION: 20, DB_STORE_NAME: "FILE_DATA", saveFilesToDB: function (paths, onload, onerror) {
onload = onload || function () { };
onerror = onerror || function () { };
var indexedDB = FS.indexedDB();
try {
var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION);
} catch (e) {
return onerror(e);
}
openRequest.onupgradeneeded = function openRequest_onupgradeneeded() {
console.log('creating db');
var db = openRequest.result;
db.createObjectStore(FS.DB_STORE_NAME);
};
openRequest.onsuccess = function openRequest_onsuccess() {
var db = openRequest.result;
var transaction = db.transaction([FS.DB_STORE_NAME], 'readwrite');
var files = transaction.objectStore(FS.DB_STORE_NAME);
var ok = 0, fail = 0, total = paths.length;
function finish() {
if (fail == 0) onload(); else onerror();
}
paths.forEach(function (path) {
var putRequest = files.put(FS.analyzePath(path).object.contents, path);
putRequest.onsuccess = function putRequest_onsuccess() { ok++; if (ok + fail == total) finish() };
putRequest.onerror = function putRequest_onerror() { fail++; if (ok + fail == total) finish() };
});
transaction.onerror = onerror;
};
openRequest.onerror = onerror;
}, loadFilesFromDB: function (paths, onload, onerror) {
onload = onload || function () { };
onerror = onerror || function () { };
var indexedDB = FS.indexedDB();
try {
var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION);
} catch (e) {
return onerror(e);
}
openRequest.onupgradeneeded = onerror; // no database to load from
openRequest.onsuccess = function openRequest_onsuccess() {
var db = openRequest.result;
try {
var transaction = db.transaction([FS.DB_STORE_NAME], 'readonly');
} catch (e) {
onerror(e);
return;
}
var files = transaction.objectStore(FS.DB_STORE_NAME);
var ok = 0, fail = 0, total = paths.length;
function finish() {
if (fail == 0) onload(); else onerror();
}
paths.forEach(function (path) {
var getRequest = files.get(path);
getRequest.onsuccess = function getRequest_onsuccess() {
if (FS.analyzePath(path).exists) {
FS.unlink(path);
}
FS.createDataFile(PATH.dirname(path), PATH.basename(path), getRequest.result, true, true, true);
ok++;
if (ok + fail == total) finish();
};
getRequest.onerror = function getRequest_onerror() { fail++; if (ok + fail == total) finish() };
});
transaction.onerror = onerror;
};
openRequest.onerror = onerror;
}
},
}; var PATH = {
splitPath: function (filename) {
var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;

View File

@ -1,37 +0,0 @@
/* global window, dcodeIO, textsecure */
// eslint-disable-next-line func-names
(function() {
window.textsecure = window.textsecure || {};
window.textsecure.protobuf = {};
function loadProtoBufs(filename) {
return dcodeIO.ProtoBuf.loadProtoFile(
{ root: window.PROTO_ROOT, file: filename },
(error, result) => {
if (error) {
const text = `Error loading protos from ${filename} (root: ${window.PROTO_ROOT}) ${
error && error.stack ? error.stack : error
}`;
window.log.error(text);
throw error;
}
const protos = result.build('signalservice');
if (!protos) {
const text = `Error loading protos from ${filename} (root: ${window.PROTO_ROOT})`;
window.log.error(text);
throw new Error(text);
}
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const protoName in protos) {
textsecure.protobuf[protoName] = protos[protoName];
}
}
);
}
// this is all the Session Protocols
loadProtoBufs('SignalService.proto');
// this is for websocket wrapping of messages
loadProtoBufs('SubProtocol.proto');
})();

View File

@ -1,100 +0,0 @@
/* global window, StringView */
/* eslint-disable no-bitwise, no-nested-ternary, */
// eslint-disable-next-line func-names
(function() {
window.StringView = {
/*
* These functions from the Mozilla Developer Network
* and have been placed in the public domain.
* https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
* https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses
*/
b64ToUint6(nChr) {
return nChr > 64 && nChr < 91
? nChr - 65
: nChr > 96 && nChr < 123
? nChr - 71
: nChr > 47 && nChr < 58
? nChr + 4
: nChr === 43
? 62
: nChr === 47
? 63
: 0;
},
// This is not a "standard" base64, do not use!
base64ToBytes(sBase64, nBlocksSize) {
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, '');
const nInLen = sB64Enc.length;
const nOutLen = nBlocksSize
? Math.ceil(((nInLen * 3 + 1) >> 2) / nBlocksSize) * nBlocksSize
: (nInLen * 3 + 1) >> 2;
const aBBytes = new ArrayBuffer(nOutLen);
const taBytes = new Uint8Array(aBBytes);
let nMod3;
let nMod4;
for (let nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx += 1) {
nMod4 = nInIdx & 3;
nUint24 |= StringView.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (18 - 6 * nMod4);
if (nMod4 === 3 || nInLen - nInIdx === 1) {
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3 += 1, nOutIdx += 1) {
taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
}
nUint24 = 0;
}
}
return aBBytes;
},
uint6ToB64(nUint6) {
return nUint6 < 26
? nUint6 + 65
: nUint6 < 52
? nUint6 + 71
: nUint6 < 62
? nUint6 - 4
: nUint6 === 62
? 43
: nUint6 === 63
? 47
: 65;
},
bytesToBase64(aBytes) {
let nMod3;
let sB64Enc = '';
for (let nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx += 1) {
nMod3 = nIdx % 3;
if (nIdx > 0 && ((nIdx * 4) / 3) % 76 === 0) {
sB64Enc += '\r\n';
}
nUint24 |= aBytes[nIdx] << ((16 >>> nMod3) & 24);
if (nMod3 === 2 || aBytes.length - nIdx === 1) {
sB64Enc += String.fromCharCode(
StringView.uint6ToB64((nUint24 >>> 18) & 63),
StringView.uint6ToB64((nUint24 >>> 12) & 63),
StringView.uint6ToB64((nUint24 >>> 6) & 63),
StringView.uint6ToB64(nUint24 & 63)
);
nUint24 = 0;
}
}
return sB64Enc.replace(/A(?=A$|$)/g, '=');
},
arrayBufferToHex(aArrayBuffer) {
return Array.prototype.map
.call(new Uint8Array(aArrayBuffer), x => `00${x.toString(16)}`.slice(-2))
.join('');
},
hexToArrayBuffer(aString) {
return new Uint8Array(aString.match(/[\da-f]{2}/gi).map(h => parseInt(h, 16))).buffer;
},
};
})();

23
main.js
View File

@ -597,12 +597,10 @@ async function showDebugLogWindow() {
debugLogWindow.loadURL(prepareURL([__dirname, 'debug_log.html'], { theme }));
debugLogWindow.on('closed', () => {
removeDarkOverlay();
debugLogWindow = null;
});
debugLogWindow.once('ready-to-show', () => {
addDarkOverlay();
debugLogWindow.show();
});
}
@ -820,14 +818,6 @@ ipc.on('locale-data', event => {
event.returnValue = locale.messages;
});
ipc.on('set-badge-count', (event, count) => {
app.setBadgeCount(count);
});
ipc.on('remove-setup-menu-items', () => {
setupMenu();
});
ipc.on('add-setup-menu-items', () => {
setupMenu({
includeSetup: false,
@ -957,19 +947,6 @@ ipc.on('close-debug-log', () => {
}
});
// Settings-related IPC calls
function addDarkOverlay() {
if (mainWindow && mainWindow.webContents) {
mainWindow.webContents.send('add-dark-overlay');
}
}
function removeDarkOverlay() {
if (mainWindow && mainWindow.webContents) {
mainWindow.webContents.send('remove-dark-overlay');
}
}
// This should be called with an ipc sendSync
ipc.on('get-media-permissions', event => {
// eslint-disable-next-line no-param-reassign

View File

View File

@ -175,7 +175,6 @@
"@types/webpack": "^5.28.0",
"arraybuffer-loader": "1.0.3",
"asar": "0.14.0",
"bower": "1.8.2",
"chai": "4.3.4",
"chai-as-promised": "^7.1.1",
"chai-bytes": "^0.1.2",

View File

@ -2,18 +2,12 @@
/* global Whisper: false */
/* global window: false */
const path = require('path');
const electron = require('electron');
const { webFrame } = electron;
const { webFrame, remote, clipboard, ipcRenderer } = require('electron');
const semver = require('semver');
const { deferredToPromise } = require('./js/modules/deferred_to_promise');
const { JobQueue } = require('./js/modules/job_queue');
const { app } = electron.remote;
const { clipboard } = electron;
window.PROTO_ROOT = 'protos';
const { app } = remote;
const config = require('url').parse(window.location.toString(), true).query;
@ -43,7 +37,6 @@ window.getCommitHash = () => config.commitHash;
window.getNodeVersion = () => config.node_version;
window.getHostName = () => config.hostname;
window.getServerTrustRoot = () => config.serverTrustRoot;
window.JobQueue = JobQueue;
window.isBehindProxy = () => Boolean(config.proxyUrl);
window.lokiFeatureFlags = {
@ -83,7 +76,7 @@ window.versionInfo = {
window.wrapDeferred = deferredToPromise;
const ipc = electron.ipcRenderer;
const ipc = ipcRenderer;
const localeMessages = ipc.sendSync('locale-data');
window.updateZoomFactor = () => {
@ -99,8 +92,6 @@ window.getZoomFactor = () => {
webFrame.getZoomFactor();
};
window.setBadgeCount = count => ipc.send('set-badge-count', count);
// Set the password for the database
window.setPassword = (passPhrase, oldPhrase) =>
new Promise((resolve, reject) => {
@ -170,21 +161,6 @@ ipc.on('get-theme-setting', () => {
ipc.send('get-success-theme-setting', theme);
});
// Settings-related events
ipc.on('add-dark-overlay', () => {
const { addDarkOverlay } = window.Events;
if (addDarkOverlay) {
addDarkOverlay();
}
});
ipc.on('remove-dark-overlay', () => {
const { removeDarkOverlay } = window.Events;
if (removeDarkOverlay) {
removeDarkOverlay();
}
});
window.getSettingValue = (settingID, comparisonValue = null) => {
// Comparison value allows you to pull boolean values from any type.
// Eg. window.getSettingValue('theme', 'light')
@ -242,9 +218,6 @@ ipc.on('get-ready-for-shutdown', async () => {
}
});
window.addSetupMenuItems = () => ipc.send('add-setup-menu-items');
window.removeSetupMenuItems = () => ipc.send('remove-setup-menu-items');
// We pull these dependencies in now, from here, because they have Node.js dependencies
require('./js/logging');

View File

@ -37,17 +37,6 @@ audio {
max-width: 100%;
}
.dark-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: $color-black;
opacity: 0.25;
z-index: 200;
}
.clearfix:before,
.clearfix:after {
display: table;

View File

@ -33,11 +33,6 @@
}
// _global
.dark-overlay {
background-color: $color-gray-95;
}
.title-bar {
color: $color-dark-05;
}

View File

@ -1,7 +1,6 @@
import React from 'react';
import Electron from 'electron';
const { shell } = Electron;
import { shell } from 'electron';
import { useDispatch, useSelector } from 'react-redux';

View File

@ -323,29 +323,33 @@ export function getMarkAllReadMenuItem(conversationId: string): JSX.Element | nu
}
export function getStartCallMenuItem(conversationId: string): JSX.Element | null {
const canCall = !(useSelector(getHasIncomingCall) || useSelector(getHasOngoingCall));
return (
<Item
onClick={async () => {
// TODO: either pass param to callRecipient or call different call methods based on item selected.
// TODO: one time redux-persisted permission modal?
const convo = getConversationController().get(conversationId);
if (window?.lokiFeatureFlags.useCallMessage) {
const canCall = !(useSelector(getHasIncomingCall) || useSelector(getHasOngoingCall));
return (
<Item
onClick={async () => {
// TODO: either pass param to callRecipient or call different call methods based on item selected.
// TODO: one time redux-persisted permission modal?
const convo = getConversationController().get(conversationId);
if (!canCall) {
ToastUtils.pushUnableToCall();
return;
}
if (!canCall) {
ToastUtils.pushUnableToCall();
return;
}
if (convo) {
convo.callState = 'connecting';
await convo.commit();
await CallManager.USER_callRecipient(convo.id);
}
}}
>
{'Video Call'}
</Item>
);
if (convo) {
convo.callState = 'connecting';
await convo.commit();
await CallManager.USER_callRecipient(convo.id);
}
}}
>
{'Video Call'}
</Item>
);
}
return null;
}
export function getDisappearingMenuItem(

View File

@ -1,6 +1,5 @@
import Electron from 'electron';
import { ipcRenderer } from 'electron';
const { ipcRenderer } = Electron;
// tslint:disable: no-require-imports no-var-requires one-variable-per-declaration no-void-expression
import _ from 'lodash';

View File

@ -6,13 +6,12 @@ import _ from 'lodash';
import fs from 'fs';
import path from 'path';
import tls from 'tls';
import Electron from 'electron';
import { remote } from 'electron';
import { sha256 } from '../crypto';
import * as Data from '../../../ts/data/data';
import pRetry from 'p-retry';
import { SeedNodeAPI } from '.';
const { remote } = Electron;
// tslint:disable: function-name
export type SeedNode = {

View File

@ -465,7 +465,13 @@ export async function retrieveNextMessages(
// let exceptions bubble up
// no retry for this one as this a call we do every few seconds while polling for messages
const result = await snodeRpc({ method: 'retrieve', params, targetNode, associatedWith });
const result = await snodeRpc({
method: 'retrieve',
params,
targetNode,
associatedWith,
timeout: 4000,
});
if (!result) {
window?.log?.warn(
@ -662,7 +668,7 @@ export const forceNetworkDeletion = async (): Promise<Array<string> | null> => {
minTimeout: exports.TEST_getMinTimeout(),
onFailedAttempt: e => {
window?.log?.warn(
`delete_all OUTER request attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left...`
`delete_all OUTER request attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left... ${e.message}`
);
},
}
@ -770,6 +776,12 @@ export const networkDeleteMessages = async (hashes: Array<string>): Promise<any>
snodeToMakeRequestTo.pubkey_ed25519
)} due to error: ${reason}: ${statusCode}`
);
// if we tried to make the delete on a snode not in our swarm, just trigger a pRetry error so the outer block here finds new snodes to make the request to.
if (statusCode === 421) {
throw new pRetry.AbortError(
'421 error on network delete_all. Retrying with a new snode'
);
}
} else {
window?.log?.warn(
`Could not delete data from ${ed25519Str(

View File

@ -19,13 +19,14 @@ async function lokiFetch({
url,
associatedWith,
targetNode,
timeout,
}: {
url: string;
options: FetchOptions;
targetNode?: Snode;
associatedWith?: string;
timeout: number;
}): Promise<undefined | SnodeResponse> {
const timeout = 10000;
const method = options.method || 'GET';
const fetchOptions = {
@ -97,11 +98,13 @@ export async function snodeRpc(
params,
targetNode,
associatedWith,
timeout = 10000,
}: {
method: string;
params: any;
targetNode: Snode;
associatedWith?: string;
timeout?: number;
} //the user pubkey this call is for. if the onion request fails, this is used to handle the error for this user swarm for instance
): Promise<undefined | SnodeResponse> {
const url = `https://${targetNode.ip}:${targetNode.port}/storage_rpc/v1`;
@ -135,5 +138,6 @@ export async function snodeRpc(
options: fetchOptions,
targetNode,
associatedWith,
timeout,
});
}

View File

@ -530,9 +530,8 @@ async function handle421InvalidSwarm({
// this does not make much sense to have a 421 without a publicKey set.
throw new Error('status 421 without a final destination or no associatedWith makes no sense');
}
window?.log?.info(`Invalidating swarm for ${associatedWith}`);
window?.log?.info(`Invalidating swarm for ${ed25519Str(associatedWith)}`);
const exceptionMessage = '421 handled. Retry this request with a new targetNode';
try {
const parsedBody = JSON.parse(body);
@ -545,12 +544,12 @@ async function handle421InvalidSwarm({
);
await updateSwarmFor(associatedWith, parsedBody.snodes);
throw new pRetry.AbortError(exceptionMessage);
throw new pRetry.AbortError(ERROR_421_HANDLED_RETRY_REQUEST);
}
// remove this node from the swarm of this pubkey
await dropSnodeFromSwarmIfNeeded(associatedWith, snodeEd25519);
} catch (e) {
if (e.message !== exceptionMessage) {
if (e.message !== ERROR_421_HANDLED_RETRY_REQUEST) {
window?.log?.warn(
'Got error while parsing 421 result. Dropping this snode from the swarm of this pubkey',
e

View File

@ -289,7 +289,7 @@ export class SwarmPolling {
retries: 1,
onFailedAttempt: e => {
window?.log?.warn(
`retrieveNextMessages attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left...`
`retrieveNextMessages attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left... ${e.name}`
);
},
}

View File

@ -2,7 +2,6 @@ import { v4 as uuid } from 'uuid';
type Job<ResultType> = (() => PromiseLike<ResultType>) | (() => ResultType);
// TODO: This needs to replace js/modules/job_queue.js
export class JobQueue {
private pending?: Promise<any> = Promise.resolve();
private readonly jobs: Map<string, Promise<unknown>> = new Map();

View File

@ -1,35 +0,0 @@
import ByteBuffer from 'bytebuffer';
/**
* Converts any object to a valid ts protobuf object.
*
* This is needed because there's a very jarring difference between `protobufjs` and `protobufts`.
* `protobufjs` returns all `bytes` as `ByteBuffer` where as `protobufts` returns all `bytes` as `Uint8Array`.
*/
export function convertToTS(object: any): any {
// No idea why js `ByteBuffer` and ts `ByteBuffer` differ ...
if (object instanceof Uint8Array) {
return object;
} else if (object && object.constructor && object.constructor.name === 'ByteBuffer') {
return new Uint8Array(object.toArrayBuffer());
} else if (
object instanceof ByteBuffer ||
object instanceof Buffer ||
object instanceof ArrayBuffer ||
object instanceof SharedArrayBuffer
) {
const arrayBuffer = ByteBuffer.wrap(object).toArrayBuffer();
return new Uint8Array(arrayBuffer);
} else if (Array.isArray(object)) {
return object.map(convertToTS);
} else if (object && typeof object === 'object') {
const keys = Object.keys(object);
const values: { [key: string]: any } = {};
for (const key of keys) {
values[key] = convertToTS(object[key]);
}
return values;
}
return object;
}

View File

@ -2,7 +2,6 @@ import * as MessageUtils from './Messages';
import * as GroupUtils from './Groups';
import * as StringUtils from './String';
import * as PromiseUtils from './Promise';
import * as ProtobufUtils from './Protobuf';
import * as MenuUtils from '../../components/session/menu/Menu';
import * as ToastUtils from './Toast';
import * as UserUtils from './User';
@ -20,7 +19,6 @@ export {
GroupUtils,
StringUtils,
PromiseUtils,
ProtobufUtils,
MenuUtils,
ToastUtils,
UserUtils,

View File

@ -1,29 +0,0 @@
// tslint:disable no-console
import { join } from 'path';
import { fromPairs, groupBy, map } from 'lodash';
import { ExceptionType } from './types';
import { loadJSON } from './util';
const exceptionsPath = join(__dirname, 'exceptions.json');
const exceptions: Array<ExceptionType> = loadJSON(exceptionsPath);
const byRule = groupBy(exceptions, 'rule');
const byRuleThenByCategory = fromPairs(
map(byRule, (list, ruleName) => {
const byCategory = groupBy(list, 'reasonCategory');
return [
ruleName,
fromPairs(
map(byCategory, (innerList, categoryName) => {
return [categoryName, innerList.length];
})
),
];
})
);
console.log(JSON.stringify(byRuleThenByCategory, null, ' '));

File diff suppressed because it is too large Load Diff

View File

@ -1,266 +0,0 @@
// tslint:disable no-console
import { readFileSync } from 'fs';
import { join, relative } from 'path';
// @ts-ignore
import * as glob from 'glob';
import { forEach, some, values } from 'lodash';
import { ExceptionType, REASONS, RuleType } from './types';
import { ENCODING, loadJSON, sortExceptions } from './util';
const ALL_REASONS = REASONS.join('|');
const now = new Date();
function getExceptionKey(exception: any) {
return `${exception.rule}-${exception.path}-${exception.lineNumber}`;
}
function createLookup(list: Array<any>) {
const lookup = Object.create(null);
forEach(list, exception => {
const key = getExceptionKey(exception);
if (lookup[key]) {
throw new Error(`Duplicate exception found for key ${key}`);
}
lookup[key] = exception;
});
return lookup;
}
const rulesPath = join(__dirname, 'rules.json');
const exceptionsPath = join(__dirname, 'exceptions.json');
const basePath = join(__dirname, '../../..');
const searchPattern = join(basePath, '**/*.{js,ts,tsx}');
const rules: Array<RuleType> = loadJSON(rulesPath);
const exceptions: Array<ExceptionType> = loadJSON(exceptionsPath);
const exceptionsLookup = createLookup(exceptions);
let scannedCount = 0;
const allSourceFiles = glob.sync(searchPattern, { nodir: true });
const results: Array<ExceptionType> = [];
const excludedFiles = [
// High-traffic files in our project
'^js/background.js',
// Generated files
'^js/components.js',
'^js/curve/',
'^js/libtextsecure.js',
'^js/util_worker.js',
'^libtextsecure/components.js',
'^libtextsecure/test/test.js',
'^test/test.js',
// From libsignal-protocol-javascript project
'^libtextsecure/libsignal-protocol.js',
// Test files
'^libtextsecure/test/*',
'^test/*',
// Modules we trust
'^node_modules/react/*',
'^node_modules/react-dom/*',
// Modules used only in test/development scenarios
'^node_modules/@types/*',
'^node_modules/ajv/*',
'^node_modules/amdefine/*',
'^node_modules/anymatch/*',
'^node_modules/app-builder-lib/*',
'^node_modules/asn1\\.js/*',
'^node_modules/autoprefixer/*',
'^node_modules/babel*',
'^node_modules/bluebird/*',
'^node_modules/body-parser/*',
'^node_modules/bower/*',
'^node_modules/buble/*',
'^node_modules/builder-util/*',
'^node_modules/builder-util-runtime/*',
'^node_modules/chai/*',
'^node_modules/cli-table2/*',
'^node_modules/codemirror/*',
'^node_modules/coffee-script/*',
'^node_modules/compression/*',
'^node_modules/degenerator/*',
'^node_modules/detect-port-alt/*',
'^node_modules/electron-builder/*',
'^node_modules/electron-osx-sign/*',
'^node_modules/electron-publish/*',
'^node_modules/escodegen/*',
'^node_modules/eslint*',
'^node_modules/esprima/*',
'^node_modules/express/*',
'^node_modules/finalhandler/*',
'^node_modules/fsevents/*',
'^node_modules/globule/*',
'^node_modules/grunt*',
'^node_modules/handle-thing/*',
'^node_modules/har-validator/*',
'^node_modules/highlight\\.js/*',
'^node_modules/hpack\\.js/*',
'^node_modules/http-proxy-middlewar/*',
'^node_modules/icss-utils/*',
'^node_modules/istanbul*',
'^node_modules/jimp/*',
'^node_modules/jquery/*',
'^node_modules/jss/*',
'^node_modules/jss-global/*',
'^node_modules/livereload-js/*',
'^node_modules/lolex/*',
'^node_modules/magic-string/*',
'^node_modules/mocha/*',
'^node_modules/minimatch/*',
'^node_modules/nise/*',
'^node_modules/node-sass-import-once/*',
'^node_modules/node-sass/*',
'^node_modules/nsp/*',
'^node_modules/phantomjs-prebuilt/*',
'^node_modules/postcss*',
'^node_modules/preserve/*',
'^node_modules/prettier/*',
'^node_modules/protobufjs/cli/*',
'^node_modules/ramda/*',
'^node_modules/react-docgen/*',
'^node_modules/react-error-overlay/*',
'^node_modules/recast/*',
'^node_modules/reduce-css-calc/*',
'^node_modules/resolve/*',
'^node_modules/sass-graph/*',
'^node_modules/scss-tokenizer/*',
'^node_modules/send/*',
'^node_modules/serve-index/*',
'^node_modules/sinon/*',
'^node_modules/snapdragon-util/*',
'^node_modules/snapdragon/*',
'^node_modules/sockjs-client/*',
'^node_modules/style-loader/*',
'^node_modules/svgo/*',
'^node_modules/text-encoding/*',
'^node_modules/tinycolor2/*',
'^node_modules/to-ast/*',
'^node_modules/trough/*',
'^node_modules/ts-loader/*',
'^node_modules/tslint*',
'^node_modules/tweetnacl/*',
'^node_modules/typescript/*',
'^node_modules/uglify-es/*',
'^node_modules/uglify-js/*',
'^node_modules/use/*',
'^node_modules/vary/*',
'^node_modules/vm-browserify/*',
'^node_modules/webdriverio/*',
'^node_modules/webpack*',
'^node_modules/xmldom/*',
'^node_modules/xml-parse-from-string/*',
];
function setupRules(allRules: Array<RuleType>) {
forEach(allRules, (rule, index) => {
if (!rule.name) {
throw new Error(`Rule at index ${index} is missing a name`);
}
if (!rule.expression) {
throw new Error(`Rule '${rule.name}' is missing an expression`);
}
rule.regex = new RegExp(rule.expression, 'g');
});
}
setupRules(rules);
forEach(allSourceFiles, file => {
const relativePath = relative(basePath, file).replace(/\\/g, '/');
if (
some(excludedFiles, excluded => {
const regex = new RegExp(excluded);
return regex.test(relativePath);
})
) {
return;
}
scannedCount += 1;
const fileContents = readFileSync(file, ENCODING);
const lines = fileContents.split('\n');
forEach(rules, (rule: RuleType) => {
const excludedModules = rule.excludedModules || [];
if (some(excludedModules, module => relativePath.startsWith(module))) {
return;
}
forEach(lines, (rawLine, lineIndex) => {
const line = rawLine.replace(/\r/g, '');
if (!rule.regex.test(line)) {
return;
}
const path = relativePath;
const lineNumber = lineIndex + 1;
const exceptionKey = getExceptionKey({
rule: rule.name,
path: relativePath,
lineNumber,
});
const exception = exceptionsLookup[exceptionKey];
if (exception && (!exception.line || exception.line === line)) {
// tslint:disable-next-line no-dynamic-delete
delete exceptionsLookup[exceptionKey];
return;
}
results.push({
rule: rule.name,
path,
line: line.length < 300 ? line : undefined,
lineNumber,
reasonCategory: ALL_REASONS,
updated: now.toJSON(),
reasonDetail: '<optional>',
});
});
});
});
const unusedExceptions = values(exceptionsLookup);
console.log(
`${scannedCount} files scanned.`,
`${results.length} questionable lines,`,
`${unusedExceptions.length} unused exceptions,`,
`${exceptions.length} total exceptions.`
);
if (results.length === 0 && unusedExceptions.length === 0) {
process.exit();
}
console.log();
console.log('Questionable lines:');
console.log(JSON.stringify(sortExceptions(results), null, ' '));
if (unusedExceptions.length) {
console.log();
console.log('Unused exceptions!');
console.log(JSON.stringify(sortExceptions(unusedExceptions), null, ' '));
}
process.exit(1);

View File

@ -1,135 +0,0 @@
[
{
"name": "eval",
"expression": "\\beval\\(",
"reason": "Arbitrary code execution"
},
{
"name": "DOM-innerHTML",
"expression": "\\binnerHTML\\b",
"reason": "Potential XSS"
},
{
"name": "DOM-outerHTML",
"expression": "\\bouterHTML\\b",
"reason": "Potential XSS"
},
{
"name": "DOM-document.write(",
"expression": "\\bdocument.write(ln)?\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-$(",
"expression": "\\$\\(",
"reason": "Potential XSS",
"excludedModules": ["node_modules/prelude-ls"]
},
{
"name": "jQuery-html(",
"expression": "\\bhtml\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-append(",
"expression": "\\bappend\\(",
"reason": "Potential XSS",
"excludedModules": [
"components/bytebuffer",
"components/protobuf",
"node_modules/google-libphonenumber",
"node_modules/handlebars"
]
},
{
"name": "jQuery-appendTo(",
"expression": "\\bappendTo\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-insertAfter(",
"expression": "\\binsertAfter\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-insertBefore(",
"expression": "\\binsertBefore\\(",
"reason": "Potential XSS",
"excludedModules": ["node_modules/react-dom"]
},
{
"name": "jQuery-prepend(",
"expression": "\\bprepend\\(",
"reason": "Potential XSS",
"excludedModules": ["components/bytebuffer", "node_modules/handlebars"]
},
{
"name": "jQuery-prependTo(",
"expression": "\\bprependTo\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-wrap(",
"expression": "\\bwrap\\(",
"reason": "Potential XSS",
"excludedModules": [
"components/bytebuffer",
"components/protobuf",
"node_modules/handlebars",
"node_modules/lodash"
]
},
{
"name": "jQuery-wrapInner(",
"expression": "\\bwrapInner\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-wrapAll(",
"expression": "\\bwrapAll\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-before(",
"expression": "\\bbefore\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-after(",
"expression": "\\bafter\\(",
"reason": "Potential XSS"
},
{
"name": "jQuery-globalEval(",
"expression": "\\bglobalEval\\(",
"reason": "Arbitrary code execution"
},
{
"name": "jQuery-getScript(",
"expression": "\\bgetScript\\(",
"reason": "Arbitrary code execution"
},
{
"name": "jQuery-load(",
"expression": "\\bload\\(",
"reason": "Arbitrary code execution"
},
{
"name": "fbjs-createNodesFromMarkup",
"expression": "\\bcreateNodesFromMarkup\\b",
"reason": "Potential XSS, pipes input to innerHTML",
"excludedModules": ["node_modules/react-dom", "node_modules/fbjs"]
},
{
"name": "thenify-multiArgs",
"expression": "\\bmultiArgs\\b",
"reason": "Potential arbitrary code execution, piped to eval",
"excludedModules": ["node_modules/thenify"]
},
{
"name": "bluebird-toFastProperties",
"expression": "\\btoFastProperties\\b",
"reason": "Whatever is provided is sent straight to eval()",
"excludedModules": []
}
]

View File

@ -1,14 +0,0 @@
// tslint:disable no-console
import { join } from 'path';
import { writeFileSync } from 'fs';
import { ExceptionType } from './types';
import { loadJSON, sortExceptions } from './util';
const exceptionsPath = join(__dirname, 'exceptions.json');
const exceptions: Array<ExceptionType> = loadJSON(exceptionsPath);
const sorted = sortExceptions(exceptions);
writeFileSync(exceptionsPath, JSON.stringify(sorted, null, ' '));

View File

@ -1,63 +0,0 @@
// Tool requirements:
// - Feed it a set of regular expressions with descriptions as to what the risks are
// - Feed it also a set of exceptions
// - It would tell us if there were any new matches that didn't already have exceptions
//
// Rules:
// {
// "name": "rule-name",
// "expression": "^regex-as-string$",
// "reason": "Reason that this expression is dangerous"
// }
//
// Categories of reasons - low to high risk:
// "falseMatch"
// "testCode"
// "exampleCode"
// "otherUtilityCode"
// "regexMatchedSafeCode"
// "notExercisedByOurApp"
// "ruleNeeded"
// "usageTrusted"
//
// Exceptions:
// [{
// "rule": "rule-name",
// "path": "path/to/filename.js",
// "lineNumber": 45,
// "reasonCategory": "<category from list above>",
// "updated": "2018-09-08T00:21:13.180Z",
// "reasonDetail": "<Optional additional information about why this is okay>"
// }]
//
// When the tool finds issues it outputs them in exception format to make it easy to add
// to the exceptions.json file
export const REASONS = [
'falseMatch',
'testCode',
'exampleCode',
'otherUtilityCode',
'regexMatchedSafeCode',
'notExercisedByOurApp',
'ruleNeeded',
'usageTrusted',
];
export type RuleType = {
name: string;
expression?: string;
reason: string;
regex: RegExp;
excludedModules?: Array<string>;
};
export type ExceptionType = {
rule: string;
path: string;
line?: string;
lineNumber: number;
reasonCategory: string;
updated: string;
reasonDetail: string;
};

View File

@ -1,24 +0,0 @@
// tslint:disable no-console
import { readFileSync } from 'fs';
import { orderBy } from 'lodash';
import { ExceptionType } from './types';
export const ENCODING = 'utf8';
export function loadJSON(target: string) {
try {
const contents = readFileSync(target, ENCODING);
return JSON.parse(contents);
} catch (error) {
console.log(`Error loading JSON from ${target}: ${error.stack}`);
throw error;
}
}
export function sortExceptions(exceptions: Array<ExceptionType>) {
return orderBy(exceptions, ['path', 'lineNumber', 'rule']);
}

2
ts/window.d.ts vendored
View File

@ -16,8 +16,6 @@ We declare window stuff here instead of global.d.ts because we are importing oth
If you import anything in global.d.ts, the type system won't work correctly.
*/
type UtilWorkerFunctionType = (fnName: string, ...args: any) => Promise<any>;
declare global {
interface Window {
CONSTANTS: any;

View File

@ -1786,11 +1786,6 @@ boolean@^3.0.0:
resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.1.tgz#35ecf2b4a2ee191b0b44986f14eb5f052a5cbb4f"
integrity sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==
bower@1.8.2:
version "1.8.2"
resolved "https://registry.yarnpkg.com/bower/-/bower-1.8.2.tgz#adf53529c8d4af02ef24fb8d5341c1419d33e2f7"
integrity sha1-rfU1KcjUrwLvJPuNU0HBQZ0z4vc=
boxen@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64"