more cleanup
This commit is contained in:
parent
d02d77a212
commit
1e8c8991ad
12
.aptly.conf
12
.aptly.conf
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -32,8 +32,7 @@ js/util_worker.js
|
||||||
libtextsecure/test/blanket_mocha.js
|
libtextsecure/test/blanket_mocha.js
|
||||||
mnemonic_languages/**
|
mnemonic_languages/**
|
||||||
|
|
||||||
# Managed by package manager (`bower` and `yarn`/`npm`):
|
# Managed by package manager (`yarn`/`npm`):
|
||||||
/bower.json
|
|
||||||
/package.json
|
/package.json
|
||||||
|
|
||||||
# Symlink into third-party `components`:
|
# Symlink into third-party `components`:
|
||||||
|
|
30
Gruntfile.js
30
Gruntfile.js
|
@ -5,18 +5,32 @@ const sass = require('node-sass');
|
||||||
|
|
||||||
/* eslint-disable more/no-then, no-console */
|
/* 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 => {
|
module.exports = grunt => {
|
||||||
const bower = grunt.file.readJSON('bower.json');
|
|
||||||
const components = [];
|
const components = [];
|
||||||
// eslint-disable-next-line guard-for-in, no-restricted-syntax
|
// eslint-disable-next-line guard-for-in, no-restricted-syntax
|
||||||
for (const i in bower.concat.app) {
|
for (const i in toConcatForApp) {
|
||||||
components.push(bower.concat.app[i]);
|
components.push(toConcatForApp[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const libtextsecurecomponents = [];
|
const libtextsecurecomponents = [];
|
||||||
// eslint-disable-next-line guard-for-in, no-restricted-syntax
|
// eslint-disable-next-line guard-for-in, no-restricted-syntax
|
||||||
for (const i in bower.concat.libtextsecure) {
|
for (const i in toConcatForComponentTextsecure) {
|
||||||
libtextsecurecomponents.push(bower.concat.libtextsecure[i]);
|
libtextsecurecomponents.push(toConcatForComponentTextsecure[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const utilWorkerComponents = [
|
const utilWorkerComponents = [
|
||||||
|
@ -58,13 +72,7 @@ module.exports = grunt => {
|
||||||
'libtextsecure/crypto.js',
|
'libtextsecure/crypto.js',
|
||||||
'libtextsecure/storage.js',
|
'libtextsecure/storage.js',
|
||||||
'libtextsecure/storage/user.js',
|
'libtextsecure/storage/user.js',
|
||||||
'libtextsecure/storage/groups.js',
|
|
||||||
'libtextsecure/protobufs.js',
|
|
||||||
'libtextsecure/helpers.js',
|
'libtextsecure/helpers.js',
|
||||||
'libtextsecure/stringview.js',
|
|
||||||
'libtextsecure/event_target.js',
|
|
||||||
'libtextsecure/http-resources.js',
|
|
||||||
'libtextsecure/message_receiver.js',
|
|
||||||
],
|
],
|
||||||
dest: 'js/libtextsecure.js',
|
dest: 'js/libtextsecure.js',
|
||||||
},
|
},
|
||||||
|
|
|
@ -453,5 +453,6 @@
|
||||||
"unableToCall": "cancel your ongoing call first",
|
"unableToCall": "cancel your ongoing call first",
|
||||||
"unableToCallTitle": "Cannot start new call",
|
"unableToCallTitle": "Cannot start new call",
|
||||||
"callMissed": "Missed call from $name$",
|
"callMissed": "Missed call from $name$",
|
||||||
"callMissedTitle": "Call missed"
|
"callMissedTitle": "Call missed",
|
||||||
|
"startVideoCall": "Start Video Call"
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,9 +32,7 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script type="text/javascript" src="js/components.js"></script>
|
<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/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/libtextsecure.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="js/focus_listener.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/read_syncs.js"></script>
|
||||||
<script type="text/javascript" src="js/expiring_messages.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/registration.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="js/views/react_wrapper_view.js"></script>
|
<script type="text/javascript" src="js/views/react_wrapper_view.js"></script>
|
||||||
|
|
|
@ -32,9 +32,7 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script type="text/javascript" src="js/components.js"></script>
|
<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/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/libtextsecure.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="js/focus_listener.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/read_syncs.js"></script>
|
||||||
<script type="text/javascript" src="js/expiring_messages.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/registration.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="js/views/react_wrapper_view.js"></script>
|
<script type="text/javascript" src="js/views/react_wrapper_view.js"></script>
|
||||||
|
|
41
bower.json
41
bower.json
|
@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 cursor’s `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
44
index.html
44
index.html
|
@ -1,23 +1,25 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class='no-js' lang='en'>
|
<html class="no-js" lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset='utf-8'>
|
<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
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"
|
||||||
<title>Session</title>
|
name="viewport"
|
||||||
<meta name="description" content="">
|
/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<link href='/images/session/session_icon_128.png' rel='shortcut icon'>
|
<title>Session</title>
|
||||||
<link href="/stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
<meta name="description" content="" />
|
||||||
<script type="text/javascript" src="js/chromium.js"></script>
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
</head>
|
<link href="/images/session/session_icon_128.png" rel="shortcut icon" />
|
||||||
<body id="signal-container" class='signal index'>
|
<link href="/stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
||||||
<div class='app-loading-screen'>
|
</head>
|
||||||
<div class="content session-full-logo">
|
<body id="signal-container" class="signal index">
|
||||||
<img src="images/session/brand.svg" class="session-brand-logo" />
|
<div class="app-loading-screen">
|
||||||
<img src="images/session/session-text.svg" class="session-text-logo" />
|
<div class="content session-full-logo">
|
||||||
</div>
|
<img src="images/session/brand.svg" class="session-brand-logo" />
|
||||||
</div>
|
<img src="images/session/session-text.svg" class="session-text-logo" />
|
||||||
<script type="text/javascript" src="js/index.js"></script>
|
</div>
|
||||||
</body>
|
</div>
|
||||||
|
<script type="text/javascript" src="js/index.js"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -66,11 +66,6 @@
|
||||||
window.getGlobalOnlineStatus = () => window.globalOnlineStatus;
|
window.getGlobalOnlineStatus = () => window.globalOnlineStatus;
|
||||||
const { Views } = window.Signal;
|
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('background page reloaded');
|
||||||
window.log.info('environment:', window.getEnvironment());
|
window.log.info('environment:', window.getEnvironment());
|
||||||
const restartReason = localStorage.getItem('restart-reason');
|
const restartReason = localStorage.getItem('restart-reason');
|
||||||
|
@ -149,15 +144,6 @@
|
||||||
storage.put('spell-check', value);
|
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 () => {
|
shutdown: async () => {
|
||||||
// Stop background processing
|
// Stop background processing
|
||||||
window.libsession.Utils.AttachmentDownloads.stop();
|
window.libsession.Utils.AttachmentDownloads.stop();
|
||||||
|
|
|
@ -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);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -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 = {
|
var NODEFS = {
|
||||||
isWindows: false,
|
isWindows: false,
|
||||||
|
@ -38010,111 +37702,6 @@ var FS = {
|
||||||
processData(url);
|
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 = {
|
var PATH = {
|
||||||
splitPath: function(filename) {
|
splitPath: function(filename) {
|
||||||
|
|
122
js/database.js
122
js/database.js
|
@ -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;
|
|
||||||
});
|
|
||||||
})();
|
|
|
@ -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();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -3,13 +3,13 @@
|
||||||
/* eslint strict: ['error', 'never'] */
|
/* eslint strict: ['error', 'never'] */
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
const electron = require('electron');
|
const { ipcRenderer } = require('electron');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
const debuglogs = require('./modules/debuglogs');
|
const debuglogs = require('./modules/debuglogs');
|
||||||
const Privacy = require('./modules/privacy');
|
const Privacy = require('./modules/privacy');
|
||||||
|
|
||||||
const ipc = electron.ipcRenderer;
|
const ipc = ipcRenderer;
|
||||||
|
|
||||||
// Default Bunyan levels: https://github.com/trentm/node-bunyan#levels
|
// 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
|
// To make it easier to visually scan logs, we make all levels the same length
|
||||||
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -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,
|
|
||||||
};
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
const Crypto = require('./crypto');
|
const Crypto = require('./crypto');
|
||||||
const Data = require('../../ts/data/data');
|
const Data = require('../../ts/data/data');
|
||||||
const Database = require('./database');
|
|
||||||
const Emoji = require('../../ts/util/emoji');
|
const Emoji = require('../../ts/util/emoji');
|
||||||
const Notifications = require('../../ts/notifications');
|
const Notifications = require('../../ts/notifications');
|
||||||
const OS = require('../../ts/OS');
|
const OS = require('../../ts/OS');
|
||||||
|
@ -143,7 +142,6 @@ exports.setup = (options = {}) => {
|
||||||
Components,
|
Components,
|
||||||
Crypto,
|
Crypto,
|
||||||
Data,
|
Data,
|
||||||
Database,
|
|
||||||
Emoji,
|
Emoji,
|
||||||
LinkPreviews,
|
LinkPreviews,
|
||||||
Migrations,
|
Migrations,
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
/* global setTimeout */
|
|
||||||
|
|
||||||
exports.sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
@ -48,7 +48,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openStandalone() {
|
openStandalone() {
|
||||||
window.addSetupMenuItems();
|
|
||||||
this.resetViews();
|
this.resetViews();
|
||||||
this.standaloneView = new Whisper.SessionRegistrationView();
|
this.standaloneView = new Whisper.SessionRegistrationView();
|
||||||
this.openView(this.standaloneView);
|
this.openView(this.standaloneView);
|
||||||
|
|
|
@ -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;
|
|
||||||
})();
|
|
|
@ -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 = {
|
var NODEFS = {
|
||||||
isWindows: false, staticInit: function () {
|
isWindows: false, staticInit: function () {
|
||||||
NODEFS.isWindows = !!process.platform.match(/^win/);
|
NODEFS.isWindows = !!process.platform.match(/^win/);
|
||||||
|
@ -4049,79 +3792,7 @@ var libsignal
|
||||||
} else {
|
} else {
|
||||||
processData(url);
|
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 = {
|
}; var PATH = {
|
||||||
splitPath: function (filename) {
|
splitPath: function (filename) {
|
||||||
var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
|
var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
|
||||||
|
|
|
@ -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');
|
|
||||||
})();
|
|
|
@ -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
23
main.js
|
@ -597,12 +597,10 @@ async function showDebugLogWindow() {
|
||||||
debugLogWindow.loadURL(prepareURL([__dirname, 'debug_log.html'], { theme }));
|
debugLogWindow.loadURL(prepareURL([__dirname, 'debug_log.html'], { theme }));
|
||||||
|
|
||||||
debugLogWindow.on('closed', () => {
|
debugLogWindow.on('closed', () => {
|
||||||
removeDarkOverlay();
|
|
||||||
debugLogWindow = null;
|
debugLogWindow = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
debugLogWindow.once('ready-to-show', () => {
|
debugLogWindow.once('ready-to-show', () => {
|
||||||
addDarkOverlay();
|
|
||||||
debugLogWindow.show();
|
debugLogWindow.show();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -820,14 +818,6 @@ ipc.on('locale-data', event => {
|
||||||
event.returnValue = locale.messages;
|
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', () => {
|
ipc.on('add-setup-menu-items', () => {
|
||||||
setupMenu({
|
setupMenu({
|
||||||
includeSetup: false,
|
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
|
// This should be called with an ipc sendSync
|
||||||
ipc.on('get-media-permissions', event => {
|
ipc.on('get-media-permissions', event => {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
|
|
@ -175,7 +175,6 @@
|
||||||
"@types/webpack": "^5.28.0",
|
"@types/webpack": "^5.28.0",
|
||||||
"arraybuffer-loader": "1.0.3",
|
"arraybuffer-loader": "1.0.3",
|
||||||
"asar": "0.14.0",
|
"asar": "0.14.0",
|
||||||
"bower": "1.8.2",
|
|
||||||
"chai": "4.3.4",
|
"chai": "4.3.4",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
"chai-bytes": "^0.1.2",
|
"chai-bytes": "^0.1.2",
|
||||||
|
|
33
preload.js
33
preload.js
|
@ -2,18 +2,12 @@
|
||||||
/* global Whisper: false */
|
/* global Whisper: false */
|
||||||
/* global window: false */
|
/* global window: false */
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const electron = require('electron');
|
const { webFrame, remote, clipboard, ipcRenderer } = require('electron');
|
||||||
|
|
||||||
const { webFrame } = electron;
|
|
||||||
const semver = require('semver');
|
const semver = require('semver');
|
||||||
|
|
||||||
const { deferredToPromise } = require('./js/modules/deferred_to_promise');
|
const { deferredToPromise } = require('./js/modules/deferred_to_promise');
|
||||||
const { JobQueue } = require('./js/modules/job_queue');
|
|
||||||
|
|
||||||
const { app } = electron.remote;
|
const { app } = remote;
|
||||||
const { clipboard } = electron;
|
|
||||||
|
|
||||||
window.PROTO_ROOT = 'protos';
|
|
||||||
|
|
||||||
const config = require('url').parse(window.location.toString(), true).query;
|
const config = require('url').parse(window.location.toString(), true).query;
|
||||||
|
|
||||||
|
@ -43,7 +37,6 @@ window.getCommitHash = () => config.commitHash;
|
||||||
window.getNodeVersion = () => config.node_version;
|
window.getNodeVersion = () => config.node_version;
|
||||||
window.getHostName = () => config.hostname;
|
window.getHostName = () => config.hostname;
|
||||||
window.getServerTrustRoot = () => config.serverTrustRoot;
|
window.getServerTrustRoot = () => config.serverTrustRoot;
|
||||||
window.JobQueue = JobQueue;
|
|
||||||
window.isBehindProxy = () => Boolean(config.proxyUrl);
|
window.isBehindProxy = () => Boolean(config.proxyUrl);
|
||||||
|
|
||||||
window.lokiFeatureFlags = {
|
window.lokiFeatureFlags = {
|
||||||
|
@ -83,7 +76,7 @@ window.versionInfo = {
|
||||||
|
|
||||||
window.wrapDeferred = deferredToPromise;
|
window.wrapDeferred = deferredToPromise;
|
||||||
|
|
||||||
const ipc = electron.ipcRenderer;
|
const ipc = ipcRenderer;
|
||||||
const localeMessages = ipc.sendSync('locale-data');
|
const localeMessages = ipc.sendSync('locale-data');
|
||||||
|
|
||||||
window.updateZoomFactor = () => {
|
window.updateZoomFactor = () => {
|
||||||
|
@ -99,8 +92,6 @@ window.getZoomFactor = () => {
|
||||||
webFrame.getZoomFactor();
|
webFrame.getZoomFactor();
|
||||||
};
|
};
|
||||||
|
|
||||||
window.setBadgeCount = count => ipc.send('set-badge-count', count);
|
|
||||||
|
|
||||||
// Set the password for the database
|
// Set the password for the database
|
||||||
window.setPassword = (passPhrase, oldPhrase) =>
|
window.setPassword = (passPhrase, oldPhrase) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
|
@ -170,21 +161,6 @@ ipc.on('get-theme-setting', () => {
|
||||||
ipc.send('get-success-theme-setting', theme);
|
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) => {
|
window.getSettingValue = (settingID, comparisonValue = null) => {
|
||||||
// Comparison value allows you to pull boolean values from any type.
|
// Comparison value allows you to pull boolean values from any type.
|
||||||
// Eg. window.getSettingValue('theme', 'light')
|
// 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
|
// We pull these dependencies in now, from here, because they have Node.js dependencies
|
||||||
|
|
||||||
require('./js/logging');
|
require('./js/logging');
|
||||||
|
|
|
@ -37,17 +37,6 @@ audio {
|
||||||
max-width: 100%;
|
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:before,
|
||||||
.clearfix:after {
|
.clearfix:after {
|
||||||
display: table;
|
display: table;
|
||||||
|
|
|
@ -33,11 +33,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// _global
|
// _global
|
||||||
|
|
||||||
.dark-overlay {
|
|
||||||
background-color: $color-gray-95;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-bar {
|
.title-bar {
|
||||||
color: $color-dark-05;
|
color: $color-dark-05;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import Electron from 'electron';
|
import { shell } from 'electron';
|
||||||
const { shell } = Electron;
|
|
||||||
|
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
|
|
@ -323,29 +323,33 @@ export function getMarkAllReadMenuItem(conversationId: string): JSX.Element | nu
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStartCallMenuItem(conversationId: string): JSX.Element | null {
|
export function getStartCallMenuItem(conversationId: string): JSX.Element | null {
|
||||||
const canCall = !(useSelector(getHasIncomingCall) || useSelector(getHasOngoingCall));
|
if (window?.lokiFeatureFlags.useCallMessage) {
|
||||||
return (
|
const canCall = !(useSelector(getHasIncomingCall) || useSelector(getHasOngoingCall));
|
||||||
<Item
|
return (
|
||||||
onClick={async () => {
|
<Item
|
||||||
// TODO: either pass param to callRecipient or call different call methods based on item selected.
|
onClick={async () => {
|
||||||
// TODO: one time redux-persisted permission modal?
|
// TODO: either pass param to callRecipient or call different call methods based on item selected.
|
||||||
const convo = getConversationController().get(conversationId);
|
// TODO: one time redux-persisted permission modal?
|
||||||
|
const convo = getConversationController().get(conversationId);
|
||||||
|
|
||||||
if (!canCall) {
|
if (!canCall) {
|
||||||
ToastUtils.pushUnableToCall();
|
ToastUtils.pushUnableToCall();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (convo) {
|
if (convo) {
|
||||||
convo.callState = 'connecting';
|
convo.callState = 'connecting';
|
||||||
await convo.commit();
|
await convo.commit();
|
||||||
await CallManager.USER_callRecipient(convo.id);
|
await CallManager.USER_callRecipient(convo.id);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{'Video Call'}
|
{'Video Call'}
|
||||||
</Item>
|
</Item>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDisappearingMenuItem(
|
export function getDisappearingMenuItem(
|
||||||
|
|
|
@ -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
|
// tslint:disable: no-require-imports no-var-requires one-variable-per-declaration no-void-expression
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
|
@ -6,13 +6,12 @@ import _ from 'lodash';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import tls from 'tls';
|
import tls from 'tls';
|
||||||
import Electron from 'electron';
|
import { remote } from 'electron';
|
||||||
import { sha256 } from '../crypto';
|
import { sha256 } from '../crypto';
|
||||||
import * as Data from '../../../ts/data/data';
|
import * as Data from '../../../ts/data/data';
|
||||||
import pRetry from 'p-retry';
|
import pRetry from 'p-retry';
|
||||||
import { SeedNodeAPI } from '.';
|
import { SeedNodeAPI } from '.';
|
||||||
|
|
||||||
const { remote } = Electron;
|
|
||||||
// tslint:disable: function-name
|
// tslint:disable: function-name
|
||||||
|
|
||||||
export type SeedNode = {
|
export type SeedNode = {
|
||||||
|
|
|
@ -465,7 +465,13 @@ export async function retrieveNextMessages(
|
||||||
|
|
||||||
// let exceptions bubble up
|
// let exceptions bubble up
|
||||||
// no retry for this one as this a call we do every few seconds while polling for messages
|
// 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) {
|
if (!result) {
|
||||||
window?.log?.warn(
|
window?.log?.warn(
|
||||||
|
@ -662,7 +668,7 @@ export const forceNetworkDeletion = async (): Promise<Array<string> | null> => {
|
||||||
minTimeout: exports.TEST_getMinTimeout(),
|
minTimeout: exports.TEST_getMinTimeout(),
|
||||||
onFailedAttempt: e => {
|
onFailedAttempt: e => {
|
||||||
window?.log?.warn(
|
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
|
snodeToMakeRequestTo.pubkey_ed25519
|
||||||
)} due to error: ${reason}: ${statusCode}`
|
)} 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 {
|
} else {
|
||||||
window?.log?.warn(
|
window?.log?.warn(
|
||||||
`Could not delete data from ${ed25519Str(
|
`Could not delete data from ${ed25519Str(
|
||||||
|
|
|
@ -19,13 +19,14 @@ async function lokiFetch({
|
||||||
url,
|
url,
|
||||||
associatedWith,
|
associatedWith,
|
||||||
targetNode,
|
targetNode,
|
||||||
|
timeout,
|
||||||
}: {
|
}: {
|
||||||
url: string;
|
url: string;
|
||||||
options: FetchOptions;
|
options: FetchOptions;
|
||||||
targetNode?: Snode;
|
targetNode?: Snode;
|
||||||
associatedWith?: string;
|
associatedWith?: string;
|
||||||
|
timeout: number;
|
||||||
}): Promise<undefined | SnodeResponse> {
|
}): Promise<undefined | SnodeResponse> {
|
||||||
const timeout = 10000;
|
|
||||||
const method = options.method || 'GET';
|
const method = options.method || 'GET';
|
||||||
|
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
|
@ -97,11 +98,13 @@ export async function snodeRpc(
|
||||||
params,
|
params,
|
||||||
targetNode,
|
targetNode,
|
||||||
associatedWith,
|
associatedWith,
|
||||||
|
timeout = 10000,
|
||||||
}: {
|
}: {
|
||||||
method: string;
|
method: string;
|
||||||
params: any;
|
params: any;
|
||||||
targetNode: Snode;
|
targetNode: Snode;
|
||||||
associatedWith?: string;
|
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
|
} //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> {
|
): Promise<undefined | SnodeResponse> {
|
||||||
const url = `https://${targetNode.ip}:${targetNode.port}/storage_rpc/v1`;
|
const url = `https://${targetNode.ip}:${targetNode.port}/storage_rpc/v1`;
|
||||||
|
@ -135,5 +138,6 @@ export async function snodeRpc(
|
||||||
options: fetchOptions,
|
options: fetchOptions,
|
||||||
targetNode,
|
targetNode,
|
||||||
associatedWith,
|
associatedWith,
|
||||||
|
timeout,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -530,9 +530,8 @@ async function handle421InvalidSwarm({
|
||||||
// this does not make much sense to have a 421 without a publicKey set.
|
// 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');
|
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 {
|
try {
|
||||||
const parsedBody = JSON.parse(body);
|
const parsedBody = JSON.parse(body);
|
||||||
|
|
||||||
|
@ -545,12 +544,12 @@ async function handle421InvalidSwarm({
|
||||||
);
|
);
|
||||||
|
|
||||||
await updateSwarmFor(associatedWith, parsedBody.snodes);
|
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
|
// remove this node from the swarm of this pubkey
|
||||||
await dropSnodeFromSwarmIfNeeded(associatedWith, snodeEd25519);
|
await dropSnodeFromSwarmIfNeeded(associatedWith, snodeEd25519);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message !== exceptionMessage) {
|
if (e.message !== ERROR_421_HANDLED_RETRY_REQUEST) {
|
||||||
window?.log?.warn(
|
window?.log?.warn(
|
||||||
'Got error while parsing 421 result. Dropping this snode from the swarm of this pubkey',
|
'Got error while parsing 421 result. Dropping this snode from the swarm of this pubkey',
|
||||||
e
|
e
|
||||||
|
|
|
@ -289,7 +289,7 @@ export class SwarmPolling {
|
||||||
retries: 1,
|
retries: 1,
|
||||||
onFailedAttempt: e => {
|
onFailedAttempt: e => {
|
||||||
window?.log?.warn(
|
window?.log?.warn(
|
||||||
`retrieveNextMessages attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left...`
|
`retrieveNextMessages attempt #${e.attemptNumber} failed. ${e.retriesLeft} retries left... ${e.name}`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
type Job<ResultType> = (() => PromiseLike<ResultType>) | (() => ResultType);
|
type Job<ResultType> = (() => PromiseLike<ResultType>) | (() => ResultType);
|
||||||
|
|
||||||
// TODO: This needs to replace js/modules/job_queue.js
|
|
||||||
export class JobQueue {
|
export class JobQueue {
|
||||||
private pending?: Promise<any> = Promise.resolve();
|
private pending?: Promise<any> = Promise.resolve();
|
||||||
private readonly jobs: Map<string, Promise<unknown>> = new Map();
|
private readonly jobs: Map<string, Promise<unknown>> = new Map();
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -2,7 +2,6 @@ import * as MessageUtils from './Messages';
|
||||||
import * as GroupUtils from './Groups';
|
import * as GroupUtils from './Groups';
|
||||||
import * as StringUtils from './String';
|
import * as StringUtils from './String';
|
||||||
import * as PromiseUtils from './Promise';
|
import * as PromiseUtils from './Promise';
|
||||||
import * as ProtobufUtils from './Protobuf';
|
|
||||||
import * as MenuUtils from '../../components/session/menu/Menu';
|
import * as MenuUtils from '../../components/session/menu/Menu';
|
||||||
import * as ToastUtils from './Toast';
|
import * as ToastUtils from './Toast';
|
||||||
import * as UserUtils from './User';
|
import * as UserUtils from './User';
|
||||||
|
@ -20,7 +19,6 @@ export {
|
||||||
GroupUtils,
|
GroupUtils,
|
||||||
StringUtils,
|
StringUtils,
|
||||||
PromiseUtils,
|
PromiseUtils,
|
||||||
ProtobufUtils,
|
|
||||||
MenuUtils,
|
MenuUtils,
|
||||||
ToastUtils,
|
ToastUtils,
|
||||||
UserUtils,
|
UserUtils,
|
||||||
|
|
|
@ -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
|
@ -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);
|
|
|
@ -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": []
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -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, ' '));
|
|
|
@ -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;
|
|
||||||
};
|
|
|
@ -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']);
|
|
||||||
}
|
|
|
@ -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.
|
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 {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
CONSTANTS: any;
|
CONSTANTS: any;
|
||||||
|
|
|
@ -1786,11 +1786,6 @@ boolean@^3.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.1.tgz#35ecf2b4a2ee191b0b44986f14eb5f052a5cbb4f"
|
resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.1.tgz#35ecf2b4a2ee191b0b44986f14eb5f052a5cbb4f"
|
||||||
integrity sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==
|
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:
|
boxen@^4.2.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64"
|
resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64"
|
||||||
|
|
Loading…
Reference in New Issue