Make errors more debuggable; capture correct stack, include name (#1944)

No more errors like this in the logs!

```
INFO  2018-01-05T18:33:15.942Z Message.saveErrors: null Error
    at file:///C:/Users/Test/AppData/Local/Programs/signal-desktop/resources/app.asar/js/libtextsecure.js:30:33
    at file:///C:/Users/Test/AppData/Local/Programs/signal-desktop/resources/app.asar/js/libtextsecure.js:138:3
    at file:///C:/Users/Test/AppData/Local/Programs/signal-desktop/resources/app.asar/js/libtextsecure.js:40718:3
```

It has no information in the title, and then the callstack points to
the `new Error()` line in the old `errors.js`.

This change will include the actual error name and message details in
the stack, and will include the original http error stack as well if
provided.
pull/749/head
Scott Nonnenberg 7 years ago committed by GitHub
parent 94a8c7e524
commit 6464d0a5fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -21,14 +21,36 @@
} }
}; };
function inherit(Parent, Child) {
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
writable: true,
configurable: true
}
});
}
function appendStack(newError, originalError) {
newError.stack += '\nOriginal stack:\n' + originalError.stack;
}
function ReplayableError(options) { function ReplayableError(options) {
options = options || {}; options = options || {};
this.name = options.name || 'ReplayableError'; this.name = options.name || 'ReplayableError';
this.message = options.message;
Error.call(this, options.message);
// Maintains proper stack trace, where our error was thrown (only available on V8)
// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
if (Error.captureStackTrace) {
Error.captureStackTrace(this);
}
this.functionCode = options.functionCode; this.functionCode = options.functionCode;
this.args = options.args; this.args = options.args;
} }
ReplayableError.prototype = new Error(); inherit(Error, ReplayableError);
ReplayableError.prototype.constructor = ReplayableError;
ReplayableError.prototype.replay = function() { ReplayableError.prototype.replay = function() {
var argumentsAsArray = Array.prototype.slice.call(arguments, 0); var argumentsAsArray = Array.prototype.slice.call(arguments, 0);
@ -37,94 +59,103 @@
}; };
function IncomingIdentityKeyError(number, message, key) { function IncomingIdentityKeyError(number, message, key) {
this.number = number.split('.')[0];
this.identityKey = key;
ReplayableError.call(this, { ReplayableError.call(this, {
functionCode : Type.INIT_SESSION, functionCode : Type.INIT_SESSION,
args : [number, message] args : [number, message],
name : 'IncomingIdentityKeyError',
message : "The identity of " + this.number + " has changed."
}); });
this.number = number.split('.')[0];
this.name = 'IncomingIdentityKeyError';
this.message = "The identity of " + this.number + " has changed.";
this.identityKey = key;
} }
IncomingIdentityKeyError.prototype = new ReplayableError(); inherit(ReplayableError, IncomingIdentityKeyError);
IncomingIdentityKeyError.prototype.constructor = IncomingIdentityKeyError;
function OutgoingIdentityKeyError(number, message, timestamp, identityKey) { function OutgoingIdentityKeyError(number, message, timestamp, identityKey) {
this.number = number.split('.')[0];
this.identityKey = identityKey;
ReplayableError.call(this, { ReplayableError.call(this, {
functionCode : Type.ENCRYPT_MESSAGE, functionCode : Type.ENCRYPT_MESSAGE,
args : [number, message, timestamp] args : [number, message, timestamp],
name : 'OutgoingIdentityKeyError',
message : "The identity of " + this.number + " has changed."
}); });
this.number = number.split('.')[0];
this.name = 'OutgoingIdentityKeyError';
this.message = "The identity of " + this.number + " has changed.";
this.identityKey = identityKey;
} }
OutgoingIdentityKeyError.prototype = new ReplayableError(); inherit(ReplayableError, OutgoingIdentityKeyError);
OutgoingIdentityKeyError.prototype.constructor = OutgoingIdentityKeyError;
function OutgoingMessageError(number, message, timestamp, httpError) { function OutgoingMessageError(number, message, timestamp, httpError) {
ReplayableError.call(this, { ReplayableError.call(this, {
functionCode : Type.ENCRYPT_MESSAGE, functionCode : Type.ENCRYPT_MESSAGE,
args : [number, message, timestamp] args : [number, message, timestamp],
name : 'OutgoingMessageError',
message : httpError ? httpError.message : 'no http error'
}); });
this.name = 'OutgoingMessageError';
if (httpError) { if (httpError) {
this.code = httpError.code; this.code = httpError.code;
this.message = httpError.message; appendStack(this, httpError);
this.stack = httpError.stack;
} }
} }
OutgoingMessageError.prototype = new ReplayableError(); inherit(ReplayableError, OutgoingMessageError);
OutgoingMessageError.prototype.constructor = OutgoingMessageError;
function SendMessageNetworkError(number, jsonData, httpError, timestamp) { function SendMessageNetworkError(number, jsonData, httpError, timestamp) {
this.number = number;
this.code = httpError.code;
ReplayableError.call(this, { ReplayableError.call(this, {
functionCode : Type.TRANSMIT_MESSAGE, functionCode : Type.TRANSMIT_MESSAGE,
args : [number, jsonData, timestamp] args : [number, jsonData, timestamp],
name : 'SendMessageNetworkError',
message : httpError.message
}); });
this.name = 'SendMessageNetworkError';
this.number = number; appendStack(this, httpError);
this.code = httpError.code;
this.message = httpError.message;
this.stack = httpError.stack;
} }
SendMessageNetworkError.prototype = new ReplayableError(); inherit(ReplayableError, SendMessageNetworkError);
SendMessageNetworkError.prototype.constructor = SendMessageNetworkError;
function SignedPreKeyRotationError(numbers, message, timestamp) { function SignedPreKeyRotationError(numbers, message, timestamp) {
ReplayableError.call(this, { ReplayableError.call(this, {
functionCode : Type.RETRY_SEND_MESSAGE_PROTO, functionCode : Type.RETRY_SEND_MESSAGE_PROTO,
args : [numbers, message, timestamp] args : [numbers, message, timestamp],
name : 'SignedPreKeyRotationError',
message : "Too many signed prekey rotation failures"
}); });
this.name = 'SignedPreKeyRotationError';
this.message = "Too many signed prekey rotation failures";
} }
SignedPreKeyRotationError.prototype = new ReplayableError(); inherit(ReplayableError, SignedPreKeyRotationError);
SignedPreKeyRotationError.prototype.constructor = SignedPreKeyRotationError;
function MessageError(message, httpError) { function MessageError(message, httpError) {
this.code = httpError.code;
ReplayableError.call(this, { ReplayableError.call(this, {
functionCode : Type.REBUILD_MESSAGE, functionCode : Type.REBUILD_MESSAGE,
args : [message] args : [message],
name : 'MessageError',
message : httpError.message
}); });
this.name = 'MessageError';
this.code = httpError.code; appendStack(this, httpError);
this.message = httpError.message;
this.stack = httpError.stack;
} }
MessageError.prototype = new ReplayableError(); inherit(ReplayableError, MessageError);
MessageError.prototype.constructor = MessageError;
function UnregisteredUserError(number, httpError) { function UnregisteredUserError(number, httpError) {
this.message = httpError.message;
this.name = 'UnregisteredUserError'; this.name = 'UnregisteredUserError';
Error.call(this, this.message);
// Maintains proper stack trace, where our error was thrown (only available on V8)
// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
if (Error.captureStackTrace) {
Error.captureStackTrace(this);
}
this.number = number; this.number = number;
this.code = httpError.code; this.code = httpError.code;
this.message = httpError.message;
this.stack = httpError.stack; appendStack(this, httpError);
} }
UnregisteredUserError.prototype = new Error(); inherit(Error, UnregisteredUserError);
UnregisteredUserError.prototype.constructor = UnregisteredUserError;
window.textsecure.UnregisteredUserError = UnregisteredUserError; window.textsecure.UnregisteredUserError = UnregisteredUserError;
window.textsecure.SendMessageNetworkError = SendMessageNetworkError; window.textsecure.SendMessageNetworkError = SendMessageNetworkError;

@ -20,14 +20,36 @@
} }
}; };
function inherit(Parent, Child) {
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
writable: true,
configurable: true
}
});
}
function appendStack(newError, originalError) {
newError.stack += '\nOriginal stack:\n' + originalError.stack;
}
function ReplayableError(options) { function ReplayableError(options) {
options = options || {}; options = options || {};
this.name = options.name || 'ReplayableError'; this.name = options.name || 'ReplayableError';
this.message = options.message;
Error.call(this, options.message);
// Maintains proper stack trace, where our error was thrown (only available on V8)
// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
if (Error.captureStackTrace) {
Error.captureStackTrace(this);
}
this.functionCode = options.functionCode; this.functionCode = options.functionCode;
this.args = options.args; this.args = options.args;
} }
ReplayableError.prototype = new Error(); inherit(Error, ReplayableError);
ReplayableError.prototype.constructor = ReplayableError;
ReplayableError.prototype.replay = function() { ReplayableError.prototype.replay = function() {
var argumentsAsArray = Array.prototype.slice.call(arguments, 0); var argumentsAsArray = Array.prototype.slice.call(arguments, 0);
@ -36,94 +58,103 @@
}; };
function IncomingIdentityKeyError(number, message, key) { function IncomingIdentityKeyError(number, message, key) {
this.number = number.split('.')[0];
this.identityKey = key;
ReplayableError.call(this, { ReplayableError.call(this, {
functionCode : Type.INIT_SESSION, functionCode : Type.INIT_SESSION,
args : [number, message] args : [number, message],
name : 'IncomingIdentityKeyError',
message : "The identity of " + this.number + " has changed."
}); });
this.number = number.split('.')[0];
this.name = 'IncomingIdentityKeyError';
this.message = "The identity of " + this.number + " has changed.";
this.identityKey = key;
} }
IncomingIdentityKeyError.prototype = new ReplayableError(); inherit(ReplayableError, IncomingIdentityKeyError);
IncomingIdentityKeyError.prototype.constructor = IncomingIdentityKeyError;
function OutgoingIdentityKeyError(number, message, timestamp, identityKey) { function OutgoingIdentityKeyError(number, message, timestamp, identityKey) {
this.number = number.split('.')[0];
this.identityKey = identityKey;
ReplayableError.call(this, { ReplayableError.call(this, {
functionCode : Type.ENCRYPT_MESSAGE, functionCode : Type.ENCRYPT_MESSAGE,
args : [number, message, timestamp] args : [number, message, timestamp],
name : 'OutgoingIdentityKeyError',
message : "The identity of " + this.number + " has changed."
}); });
this.number = number.split('.')[0];
this.name = 'OutgoingIdentityKeyError';
this.message = "The identity of " + this.number + " has changed.";
this.identityKey = identityKey;
} }
OutgoingIdentityKeyError.prototype = new ReplayableError(); inherit(ReplayableError, OutgoingIdentityKeyError);
OutgoingIdentityKeyError.prototype.constructor = OutgoingIdentityKeyError;
function OutgoingMessageError(number, message, timestamp, httpError) { function OutgoingMessageError(number, message, timestamp, httpError) {
ReplayableError.call(this, { ReplayableError.call(this, {
functionCode : Type.ENCRYPT_MESSAGE, functionCode : Type.ENCRYPT_MESSAGE,
args : [number, message, timestamp] args : [number, message, timestamp],
name : 'OutgoingMessageError',
message : httpError ? httpError.message : 'no http error'
}); });
this.name = 'OutgoingMessageError';
if (httpError) { if (httpError) {
this.code = httpError.code; this.code = httpError.code;
this.message = httpError.message; appendStack(this, httpError);
this.stack = httpError.stack;
} }
} }
OutgoingMessageError.prototype = new ReplayableError(); inherit(ReplayableError, OutgoingMessageError);
OutgoingMessageError.prototype.constructor = OutgoingMessageError;
function SendMessageNetworkError(number, jsonData, httpError, timestamp) { function SendMessageNetworkError(number, jsonData, httpError, timestamp) {
this.number = number;
this.code = httpError.code;
ReplayableError.call(this, { ReplayableError.call(this, {
functionCode : Type.TRANSMIT_MESSAGE, functionCode : Type.TRANSMIT_MESSAGE,
args : [number, jsonData, timestamp] args : [number, jsonData, timestamp],
name : 'SendMessageNetworkError',
message : httpError.message
}); });
this.name = 'SendMessageNetworkError';
this.number = number; appendStack(this, httpError);
this.code = httpError.code;
this.message = httpError.message;
this.stack = httpError.stack;
} }
SendMessageNetworkError.prototype = new ReplayableError(); inherit(ReplayableError, SendMessageNetworkError);
SendMessageNetworkError.prototype.constructor = SendMessageNetworkError;
function SignedPreKeyRotationError(numbers, message, timestamp) { function SignedPreKeyRotationError(numbers, message, timestamp) {
ReplayableError.call(this, { ReplayableError.call(this, {
functionCode : Type.RETRY_SEND_MESSAGE_PROTO, functionCode : Type.RETRY_SEND_MESSAGE_PROTO,
args : [numbers, message, timestamp] args : [numbers, message, timestamp],
name : 'SignedPreKeyRotationError',
message : "Too many signed prekey rotation failures"
}); });
this.name = 'SignedPreKeyRotationError';
this.message = "Too many signed prekey rotation failures";
} }
SignedPreKeyRotationError.prototype = new ReplayableError(); inherit(ReplayableError, SignedPreKeyRotationError);
SignedPreKeyRotationError.prototype.constructor = SignedPreKeyRotationError;
function MessageError(message, httpError) { function MessageError(message, httpError) {
this.code = httpError.code;
ReplayableError.call(this, { ReplayableError.call(this, {
functionCode : Type.REBUILD_MESSAGE, functionCode : Type.REBUILD_MESSAGE,
args : [message] args : [message],
name : 'MessageError',
message : httpError.message
}); });
this.name = 'MessageError';
this.code = httpError.code; appendStack(this, httpError);
this.message = httpError.message;
this.stack = httpError.stack;
} }
MessageError.prototype = new ReplayableError(); inherit(ReplayableError, MessageError);
MessageError.prototype.constructor = MessageError;
function UnregisteredUserError(number, httpError) { function UnregisteredUserError(number, httpError) {
this.message = httpError.message;
this.name = 'UnregisteredUserError'; this.name = 'UnregisteredUserError';
Error.call(this, this.message);
// Maintains proper stack trace, where our error was thrown (only available on V8)
// via https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
if (Error.captureStackTrace) {
Error.captureStackTrace(this);
}
this.number = number; this.number = number;
this.code = httpError.code; this.code = httpError.code;
this.message = httpError.message;
this.stack = httpError.stack; appendStack(this, httpError);
} }
UnregisteredUserError.prototype = new Error(); inherit(Error, UnregisteredUserError);
UnregisteredUserError.prototype.constructor = UnregisteredUserError;
window.textsecure.UnregisteredUserError = UnregisteredUserError; window.textsecure.UnregisteredUserError = UnregisteredUserError;
window.textsecure.SendMessageNetworkError = SendMessageNetworkError; window.textsecure.SendMessageNetworkError = SendMessageNetworkError;

@ -34,6 +34,8 @@
<script type="text/javascript" src="../task_with_timeout.js" data-cover></script> <script type="text/javascript" src="../task_with_timeout.js" data-cover></script>
<script type="text/javascript" src="fake_api.js"></script> <script type="text/javascript" src="fake_api.js"></script>
<script type="text/javascript" src="errors_test.js"></script>
<script type="text/javascript" src="helpers_test.js"></script> <script type="text/javascript" src="helpers_test.js"></script>
<script type="text/javascript" src="storage_test.js"></script> <script type="text/javascript" src="storage_test.js"></script>
<script type="text/javascript" src="crypto_test.js"></script> <script type="text/javascript" src="crypto_test.js"></script>

Loading…
Cancel
Save