refactor: moving types and functions from libweb
This commit is contained in:
parent
00abaed2ed
commit
44349bef9c
|
@ -0,0 +1,101 @@
|
|||
// errTracker.ts defines an 'ErrTracker' type which keeps track of historical
|
||||
// errors. When the number of errors gets too large, it randomly starts pruning
|
||||
// errors. It always keeps 250 of the most recent errors, and then keeps up to
|
||||
// 500 historic errors, where the first few errors after runtime are always
|
||||
// kept, and the ones in the middle are increasingly likely to be omitted from
|
||||
// the history.
|
||||
|
||||
import { Err } from "./types.js";
|
||||
|
||||
// MAX_ERRORS defines the maximum number of errors that will be held in the
|
||||
// HistoricErr object.
|
||||
const MAX_ERRORS = 1000;
|
||||
|
||||
// HistoricErr is a wrapper that adds a date to the Err type.
|
||||
interface HistoricErr {
|
||||
err: Err;
|
||||
date: Date;
|
||||
}
|
||||
|
||||
// ErrTracker keeps track of errors that have happened, randomly dropping
|
||||
// errors to prevent the tracker from using too much memory if there happen to
|
||||
// be a large number of errors.
|
||||
interface ErrTracker {
|
||||
recentErrs: HistoricErr[];
|
||||
oldErrs: HistoricErr[];
|
||||
|
||||
addErr: (err: Err) => void;
|
||||
viewErrs: () => HistoricErr[];
|
||||
}
|
||||
|
||||
// newErrTracker returns an ErrTracker object that is ready to have errors
|
||||
// added to it.
|
||||
function newErrTracker(): ErrTracker {
|
||||
const et: ErrTracker = {
|
||||
recentErrs: [],
|
||||
oldErrs: [],
|
||||
|
||||
addErr: function (err: Err): void {
|
||||
addHistoricErr(et, err);
|
||||
},
|
||||
viewErrs: function (): HistoricErr[] {
|
||||
return viewErrs(et);
|
||||
},
|
||||
};
|
||||
return et;
|
||||
}
|
||||
|
||||
// addHistoricErr is a function that will add an error to a set of historic
|
||||
// errors. It uses randomness to prune errors once the error object is too
|
||||
// large.
|
||||
function addHistoricErr(et: ErrTracker, err: Err): void {
|
||||
// Add this error to the set of most recent errors.
|
||||
et.recentErrs.push({
|
||||
err,
|
||||
date: new Date(),
|
||||
});
|
||||
|
||||
// Determine whether some of the most recent errors need to be moved into
|
||||
// logTermErrs. If the length of the mostRecentErrs is not at least half of
|
||||
// the MAX_ERRORS, we don't need to do anything.
|
||||
if (et.recentErrs.length < MAX_ERRORS / 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate through the recentErrs. For the first half of the recentErrs, we
|
||||
// will use randomness to either toss them or move them to oldErrs. The
|
||||
// second half of the recentErrs will be kept as the new recentErrs array.
|
||||
const newRecentErrs : HistoricErr[] = [];
|
||||
for (let i = 0; i < et.recentErrs.length; i++) {
|
||||
// If we are in the second half of the array, add the element to
|
||||
// newRecentErrs.
|
||||
if (i > et.recentErrs.length / 2) {
|
||||
newRecentErrs.push(et.recentErrs[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We are in the first half of the array, use a random number to add the
|
||||
// error oldErrs probabilistically.
|
||||
const rand = Math.random();
|
||||
const target = et.oldErrs.length / (MAX_ERRORS / 2);
|
||||
if (rand > target || et.oldErrs.length < 25) {
|
||||
et.oldErrs.push(et.recentErrs[i]);
|
||||
}
|
||||
}
|
||||
et.recentErrs = newRecentErrs;
|
||||
}
|
||||
|
||||
// viewErrs returns the list of errors that have been retained by the
|
||||
// HistoricErr object.
|
||||
function viewErrs(et: ErrTracker): HistoricErr[] {
|
||||
const finalErrs: HistoricErr[] = [];
|
||||
for (let i = 0; i < et.oldErrs.length; i++) {
|
||||
finalErrs.push(et.oldErrs[i]);
|
||||
}
|
||||
for (let i = 0; i < et.recentErrs.length; i++) {
|
||||
finalErrs.push(et.recentErrs[i]);
|
||||
}
|
||||
return finalErrs;
|
||||
}
|
||||
|
||||
export { ErrTracker, HistoricErr, newErrTracker };
|
|
@ -0,0 +1,87 @@
|
|||
// DataFn can take any object as input and has no return value. The input is
|
||||
// allowed to be undefined.
|
||||
type DataFn = (data?: any) => void;
|
||||
|
||||
// Err is an error type that is either a string or a null. If the value is
|
||||
// null, that indicates that there was no error. If the value is a string, it
|
||||
// indicates that there was an error and the string contains the error message.
|
||||
//
|
||||
// The skynet libraries prefer this error type to the standard Error type
|
||||
// because many times skynet libraries need to pass errors over postMessage,
|
||||
// and the 'Error' type is not able to be sent over postMessage.
|
||||
type Err = string | null;
|
||||
|
||||
// ErrFn must take an error message as input. The input is not allowed to be
|
||||
// undefined or null, there must be an error.
|
||||
type ErrFn = (errMsg: string) => void;
|
||||
|
||||
// ErrTuple is a type that pairs a 'data' field with an 'err' field. Skynet
|
||||
// libraries typically prefer returning ErrTuples to throwing or rejecting,
|
||||
// because it allows upstream code to avoid the try/catch/throw pattern. Though
|
||||
// the pattern is much celebrated in javascript, it encourages relaxed error
|
||||
// handling, and often makes error handling much more difficult because the try
|
||||
// and the catch are in different scopes.
|
||||
//
|
||||
// Most of the Skynet core libraries do not have any `throws` anywhere in their
|
||||
// API.
|
||||
//
|
||||
// Typically, an ErrTuple will have only one field filled out. If data is
|
||||
// returned, the err should be 'null'. If an error is returned, the data field
|
||||
// should generally be empty. Callers are expected to check the error before
|
||||
// they access any part of the data field.
|
||||
type ErrTuple<T = any> = [data: T, err: Err];
|
||||
|
||||
// KernelAuthStatus is the structure of a message that gets sent by the kernel
|
||||
// containing its auth status. Auth occurs in 5 stages.
|
||||
//
|
||||
// Stage 0; no auth updates
|
||||
// Stage 1: bootloader is loaded, user is not yet logged in
|
||||
// Stage 2: bootloader is loaded, user is logged in
|
||||
// Stage 3: kernel is loaded, user is logged in
|
||||
// Stage 4: kernel is loaded, user is logging out (refresh iminent)
|
||||
//
|
||||
// 'kernelLoaded' is initially set to "not yet" and will be updated when the
|
||||
// kernel has loaded. If it is set to "success", it means the kernel loaded
|
||||
// without issues. If it is set to anything else, it means that there was an
|
||||
// error, and the new value is the error.
|
||||
//
|
||||
// 'kernelLoaded' will not be changed until 'loginComplete' has been set to
|
||||
// true. 'loginComplete' can be set to true immediately if the user is already
|
||||
// logged in.
|
||||
//
|
||||
// 'logoutComplete' can be set to 'true' at any point, which indicates that the
|
||||
// auth cycle needs to reset.
|
||||
interface KernelAuthStatus {
|
||||
loginComplete: boolean;
|
||||
kernelLoaded: "not yet" | "success" | string;
|
||||
logoutComplete: boolean;
|
||||
}
|
||||
|
||||
interface Portal {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
// RequestOverrideResponse defines the type that the kernel returns as a
|
||||
// response to a requestOverride call.
|
||||
interface RequestOverrideResponse {
|
||||
override: boolean;
|
||||
headers?: any; // TODO: I don't know how to do an array of types.
|
||||
body?: Uint8Array;
|
||||
}
|
||||
|
||||
export interface KeyPair {
|
||||
publicKey: Uint8Array;
|
||||
privateKey: Uint8Array;
|
||||
}
|
||||
|
||||
export {
|
||||
DataFn,
|
||||
ErrFn,
|
||||
Err,
|
||||
ErrTuple,
|
||||
KernelAuthStatus,
|
||||
RequestOverrideResponse,
|
||||
Portal,
|
||||
};
|
|
@ -0,0 +1,78 @@
|
|||
// addContextToErr is a helper function that standardizes the formatting of
|
||||
// adding context to an error.
|
||||
//
|
||||
// NOTE: To protect against accidental situations where an Error type or some
|
||||
// other type is provided instead of a string, we wrap both of the inputs with
|
||||
// objAsString before returning them. This prevents runtime failures.
|
||||
function addContextToErr(err: any, context: string): string {
|
||||
if (err === null || err === undefined) {
|
||||
err = "[no error provided]";
|
||||
}
|
||||
return objAsString(context) + ": " + objAsString(err);
|
||||
}
|
||||
|
||||
// objAsString will try to return the provided object as a string. If the
|
||||
// object is already a string, it will be returned without modification. If the
|
||||
// object is an 'Error', the message of the error will be returned. If the object
|
||||
// has a toString method, the toString method will be called and the result
|
||||
// will be returned. If the object is null or undefined, a special string will
|
||||
// be returned indicating that the undefined/null object cannot be converted to
|
||||
// a string. In all other cases, JSON.stringify is used. If JSON.stringify
|
||||
// throws an exception, a message "[could not provide object as string]" will
|
||||
// be returned.
|
||||
//
|
||||
// NOTE: objAsString is intended to produce human readable output. It is lossy,
|
||||
// and it is not intended to be used for serialization.
|
||||
function objAsString(obj: any): string {
|
||||
// Check for undefined input.
|
||||
if (obj === undefined) {
|
||||
return "[cannot convert undefined to string]";
|
||||
}
|
||||
if (obj === null) {
|
||||
return "[cannot convert null to string]";
|
||||
}
|
||||
|
||||
// Parse the error into a string.
|
||||
if (typeof obj === "string") {
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Check if the object is an error, and return the message of the error if
|
||||
// so.
|
||||
if (obj instanceof Error) {
|
||||
return obj.message;
|
||||
}
|
||||
|
||||
// Check if the object has a 'toString' method defined on it. To ensure
|
||||
// that we don't crash or throw, check that the toString is a function, and
|
||||
// also that the return value of toString is a string.
|
||||
if (Object.prototype.hasOwnProperty.call(obj, "toString")) {
|
||||
if (typeof obj.toString === "function") {
|
||||
const str = obj.toString();
|
||||
if (typeof str === "string") {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the object does not have a custom toString, attempt to perform a
|
||||
// JSON.stringify. We use a lot of bigints in libskynet, and calling
|
||||
// JSON.stringify on an object with a bigint will cause a throw, so we add
|
||||
// some custom handling to allow bigint objects to still be encoded.
|
||||
try {
|
||||
return JSON.stringify(obj, (_, v) => {
|
||||
if (typeof v === "bigint") {
|
||||
return v.toString();
|
||||
}
|
||||
return v;
|
||||
});
|
||||
} catch (err: any) {
|
||||
if (err !== undefined && typeof err.message === "string") {
|
||||
return `[stringify failed]: ${err.message}`;
|
||||
}
|
||||
return "[stringify failed]";
|
||||
}
|
||||
}
|
||||
|
||||
export { objAsString };
|
||||
export { addContextToErr };
|
Loading…
Reference in New Issue