mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-05-08 08:01:02 -04:00
Moved SecretRotator to typescript.
This commit is contained in:
parent
d5c011e6ed
commit
a57b13d319
13 changed files with 93 additions and 41 deletions
6
src/node/models/DeriveModel.ts
Normal file
6
src/node/models/DeriveModel.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export type DeriveModel = {
|
||||||
|
digest: string,
|
||||||
|
secret: string,
|
||||||
|
salt: string,
|
||||||
|
keyLen: number
|
||||||
|
}
|
8
src/node/models/LegacyParams.ts
Normal file
8
src/node/models/LegacyParams.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export type LegacyParams = {
|
||||||
|
start: number,
|
||||||
|
end: number,
|
||||||
|
lifetime: number,
|
||||||
|
algId: number,
|
||||||
|
algParams: any,
|
||||||
|
interval:number|null
|
||||||
|
}
|
3
src/node/models/MapType.ts
Normal file
3
src/node/models/MapType.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export type MapType = {
|
||||||
|
[key: string|number]: string|number
|
||||||
|
}
|
16
src/node/models/PadType.ts
Normal file
16
src/node/models/PadType.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export type PadType = {
|
||||||
|
apool: ()=>APool,
|
||||||
|
atext: AText,
|
||||||
|
getInternalRevisionAText: (text:string)=>Promise<AText>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type APool = {
|
||||||
|
putAttrib: ([],flag: boolean)=>number
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type AText = {
|
||||||
|
text: string,
|
||||||
|
attribs: any
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import {Readable} from "node:stream";
|
import type {Readable} from "node:stream";
|
||||||
import {ChildProcess} from "node:child_process";
|
import type {ChildProcess} from "node:child_process";
|
||||||
|
|
||||||
export type PromiseWithStd = {
|
export type PromiseWithStd = {
|
||||||
stdout?: Readable|null,
|
stdout?: Readable|null,
|
||||||
|
|
|
@ -1,27 +1,32 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import {DeriveModel} from "../models/DeriveModel";
|
||||||
|
import {LegacyParams} from "../models/LegacyParams";
|
||||||
|
|
||||||
const {Buffer} = require('buffer');
|
const {Buffer} = require('buffer');
|
||||||
const crypto = require('./crypto');
|
const crypto = require('./crypto');
|
||||||
const db = require('../db/DB');
|
const db = require('../db/DB');
|
||||||
const log4js = require('log4js');
|
const log4js = require('log4js');
|
||||||
|
|
||||||
class Kdf {
|
class Kdf {
|
||||||
async generateParams() { throw new Error('not implemented'); }
|
async generateParams(): Promise<{ salt: string; digest: string; keyLen: number; secret: string }> { throw new Error('not implemented'); }
|
||||||
async derive(params, info) { throw new Error('not implemented'); }
|
async derive(params: DeriveModel, info: any) { throw new Error('not implemented'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
class LegacyStaticSecret extends Kdf {
|
class LegacyStaticSecret extends Kdf {
|
||||||
async derive(params, info) { return params; }
|
async derive(params:any, info:any) { return params; }
|
||||||
}
|
}
|
||||||
|
|
||||||
class Hkdf extends Kdf {
|
class Hkdf extends Kdf {
|
||||||
constructor(digest, keyLen) {
|
private readonly _digest: string
|
||||||
|
private readonly _keyLen: number
|
||||||
|
constructor(digest:string, keyLen:number) {
|
||||||
super();
|
super();
|
||||||
this._digest = digest;
|
this._digest = digest;
|
||||||
this._keyLen = keyLen;
|
this._keyLen = keyLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateParams() {
|
async generateParams(): Promise<{ salt: string; digest: string; keyLen: number; secret: string }> {
|
||||||
const [secret, salt] = (await Promise.all([
|
const [secret, salt] = (await Promise.all([
|
||||||
crypto.randomBytes(this._keyLen),
|
crypto.randomBytes(this._keyLen),
|
||||||
crypto.randomBytes(this._keyLen),
|
crypto.randomBytes(this._keyLen),
|
||||||
|
@ -29,7 +34,7 @@ class Hkdf extends Kdf {
|
||||||
return {digest: this._digest, keyLen: this._keyLen, salt, secret};
|
return {digest: this._digest, keyLen: this._keyLen, salt, secret};
|
||||||
}
|
}
|
||||||
|
|
||||||
async derive(p, info) {
|
async derive(p: DeriveModel, info:any) {
|
||||||
return Buffer.from(
|
return Buffer.from(
|
||||||
await crypto.hkdf(p.digest, p.secret, p.salt, info, p.keyLen)).toString('hex');
|
await crypto.hkdf(p.digest, p.secret, p.salt, info, p.keyLen)).toString('hex');
|
||||||
}
|
}
|
||||||
|
@ -48,8 +53,8 @@ const algorithms = [
|
||||||
const defaultAlgId = algorithms.length - 1;
|
const defaultAlgId = algorithms.length - 1;
|
||||||
|
|
||||||
// In JavaScript, the % operator is remainder, not modulus.
|
// In JavaScript, the % operator is remainder, not modulus.
|
||||||
const mod = (a, n) => ((a % n) + n) % n;
|
const mod = (a:number, n:number) => ((a % n) + n) % n;
|
||||||
const intervalStart = (t, interval) => t - mod(t, interval);
|
const intervalStart = (t:number, interval:number) => t - mod(t, interval);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maintains an array of secrets across one or more Etherpad instances sharing the same database,
|
* Maintains an array of secrets across one or more Etherpad instances sharing the same database,
|
||||||
|
@ -59,6 +64,14 @@ const intervalStart = (t, interval) => t - mod(t, interval);
|
||||||
* from a long-lived secret stored in the database (generated if missing).
|
* from a long-lived secret stored in the database (generated if missing).
|
||||||
*/
|
*/
|
||||||
class SecretRotator {
|
class SecretRotator {
|
||||||
|
private readonly secrets: string[];
|
||||||
|
private readonly _dbPrefix
|
||||||
|
private readonly _interval
|
||||||
|
private readonly _legacyStaticSecret
|
||||||
|
private readonly _lifetime
|
||||||
|
private readonly _logger
|
||||||
|
private _updateTimeout:any
|
||||||
|
private readonly _t
|
||||||
/**
|
/**
|
||||||
* @param {string} dbPrefix - Database key prefix to use for tracking secret metadata.
|
* @param {string} dbPrefix - Database key prefix to use for tracking secret metadata.
|
||||||
* @param {number} interval - How often to rotate in a new secret.
|
* @param {number} interval - How often to rotate in a new secret.
|
||||||
|
@ -68,7 +81,7 @@ class SecretRotator {
|
||||||
* rotation. If the oldest known secret starts after `lifetime` ago, this secret will cover
|
* rotation. If the oldest known secret starts after `lifetime` ago, this secret will cover
|
||||||
* the time period starting `lifetime` ago and ending at the start of that secret.
|
* the time period starting `lifetime` ago and ending at the start of that secret.
|
||||||
*/
|
*/
|
||||||
constructor(dbPrefix, interval, lifetime, legacyStaticSecret = null) {
|
constructor(dbPrefix: string, interval: number, lifetime: number, legacyStaticSecret:string|null = null) {
|
||||||
/**
|
/**
|
||||||
* The secrets. The first secret in this array is the one that should be used to generate new
|
* The secrets. The first secret in this array is the one that should be used to generate new
|
||||||
* MACs. All of the secrets in this array should be used when attempting to authenticate an
|
* MACs. All of the secrets in this array should be used when attempting to authenticate an
|
||||||
|
@ -94,7 +107,7 @@ class SecretRotator {
|
||||||
this._t = {now: Date.now.bind(Date), setTimeout, clearTimeout, algorithms};
|
this._t = {now: Date.now.bind(Date), setTimeout, clearTimeout, algorithms};
|
||||||
}
|
}
|
||||||
|
|
||||||
async _publish(params, id = null) {
|
async _publish(params: LegacyParams, id:string|null = null) {
|
||||||
// Params are published to the db with a randomly generated key to avoid race conditions with
|
// Params are published to the db with a randomly generated key to avoid race conditions with
|
||||||
// other instances.
|
// other instances.
|
||||||
if (id == null) id = `${this._dbPrefix}:${(await crypto.randomBytes(32)).toString('hex')}`;
|
if (id == null) id = `${this._dbPrefix}:${(await crypto.randomBytes(32)).toString('hex')}`;
|
||||||
|
@ -114,7 +127,7 @@ class SecretRotator {
|
||||||
this._updateTimeout = null;
|
this._updateTimeout = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _deriveSecrets(p, now) {
|
async _deriveSecrets(p: any, now: number) {
|
||||||
this._logger.debug('deriving secrets from', p);
|
this._logger.debug('deriving secrets from', p);
|
||||||
if (!p.interval) return [await algorithms[p.algId].derive(p.algParams, null)];
|
if (!p.interval) return [await algorithms[p.algId].derive(p.algParams, null)];
|
||||||
const t0 = intervalStart(now, p.interval);
|
const t0 = intervalStart(now, p.interval);
|
||||||
|
@ -139,7 +152,7 @@ class SecretRotator {
|
||||||
// Whether the derived secret for the interval starting at tN is still relevant. If there was no
|
// Whether the derived secret for the interval starting at tN is still relevant. If there was no
|
||||||
// clock skew, a derived secret is relevant until p.lifetime has elapsed since the end of the
|
// clock skew, a derived secret is relevant until p.lifetime has elapsed since the end of the
|
||||||
// interval. To accommodate clock skew, this end time is extended by p.interval.
|
// interval. To accommodate clock skew, this end time is extended by p.interval.
|
||||||
const expired = (tN) => now >= tN + (2 * p.interval) + p.lifetime;
|
const expired = (tN:number) => now >= tN + (2 * p.interval) + p.lifetime;
|
||||||
// Walk from t0 back until either the start of coverage or the derived secret is expired. t0
|
// Walk from t0 back until either the start of coverage or the derived secret is expired. t0
|
||||||
// must always be the first entry in case p is the current params. (The first derived secret is
|
// must always be the first entry in case p is the current params. (The first derived secret is
|
||||||
// used for generating MACs, so the secret derived for t0 must be before the secrets derived for
|
// used for generating MACs, so the secret derived for t0 must be before the secrets derived for
|
||||||
|
@ -160,12 +173,12 @@ class SecretRotator {
|
||||||
// TODO: This is racy. If two instances start up at the same time and there are no existing
|
// TODO: This is racy. If two instances start up at the same time and there are no existing
|
||||||
// matching publications, each will generate and publish their own paramters. In practice this
|
// matching publications, each will generate and publish their own paramters. In practice this
|
||||||
// is unlikely to happen, and if it does it can be fixed by restarting both Etherpad instances.
|
// is unlikely to happen, and if it does it can be fixed by restarting both Etherpad instances.
|
||||||
const dbKeys = await db.findKeys(`${this._dbPrefix}:*`, null) || [];
|
const dbKeys:string[] = await db.findKeys(`${this._dbPrefix}:*`, null) || [];
|
||||||
let currentParams = null;
|
let currentParams:any = null;
|
||||||
let currentId = null;
|
let currentId = null;
|
||||||
const dbWrites = [];
|
const dbWrites:any[] = [];
|
||||||
const allParams = [];
|
const allParams = [];
|
||||||
const legacyParams = [];
|
const legacyParams:LegacyParams[] = [];
|
||||||
await Promise.all(dbKeys.map(async (dbKey) => {
|
await Promise.all(dbKeys.map(async (dbKey) => {
|
||||||
const p = await db.get(dbKey);
|
const p = await db.get(dbKey);
|
||||||
if (p.algId === 0 && p.algParams === this._legacyStaticSecret) legacyParams.push(p);
|
if (p.algId === 0 && p.algParams === this._legacyStaticSecret) legacyParams.push(p);
|
||||||
|
@ -198,7 +211,7 @@ class SecretRotator {
|
||||||
!legacyParams.find((p) => p.end + p.lifetime >= legacyEnd + this._lifetime)) {
|
!legacyParams.find((p) => p.end + p.lifetime >= legacyEnd + this._lifetime)) {
|
||||||
const d = new Date(legacyEnd).toJSON();
|
const d = new Date(legacyEnd).toJSON();
|
||||||
this._logger.debug(`adding legacy static secret for ${d} with lifetime ${this._lifetime}`);
|
this._logger.debug(`adding legacy static secret for ${d} with lifetime ${this._lifetime}`);
|
||||||
const p = {
|
const p: LegacyParams = {
|
||||||
algId: 0,
|
algId: 0,
|
||||||
algParams: this._legacyStaticSecret,
|
algParams: this._legacyStaticSecret,
|
||||||
// The start time is equal to the end time so that this legacy secret does not affect the
|
// The start time is equal to the end time so that this legacy secret does not affect the
|
|
@ -21,13 +21,13 @@ const authorManager = require('../db/AuthorManager');
|
||||||
const hooks = require('../../static/js/pluginfw/hooks');
|
const hooks = require('../../static/js/pluginfw/hooks');
|
||||||
const padManager = require('../db/PadManager');
|
const padManager = require('../db/PadManager');
|
||||||
|
|
||||||
exports.getPadRaw = async (padId, readOnlyId) => {
|
exports.getPadRaw = async (padId:string, readOnlyId:string) => {
|
||||||
const dstPfx = `pad:${readOnlyId || padId}`;
|
const dstPfx = `pad:${readOnlyId || padId}`;
|
||||||
const [pad, customPrefixes] = await Promise.all([
|
const [pad, customPrefixes] = await Promise.all([
|
||||||
padManager.getPad(padId),
|
padManager.getPad(padId),
|
||||||
hooks.aCallAll('exportEtherpadAdditionalContent'),
|
hooks.aCallAll('exportEtherpadAdditionalContent'),
|
||||||
]);
|
]);
|
||||||
const pluginRecords = await Promise.all(customPrefixes.map(async (customPrefix) => {
|
const pluginRecords = await Promise.all(customPrefixes.map(async (customPrefix:string) => {
|
||||||
const srcPfx = `${customPrefix}:${padId}`;
|
const srcPfx = `${customPrefix}:${padId}`;
|
||||||
const dstPfx = `${customPrefix}:${readOnlyId || padId}`;
|
const dstPfx = `${customPrefix}:${readOnlyId || padId}`;
|
||||||
assert(!srcPfx.includes('*'));
|
assert(!srcPfx.includes('*'));
|
|
@ -26,7 +26,7 @@ const { checkValidRev } = require('./checkValidRev');
|
||||||
/*
|
/*
|
||||||
* This method seems unused in core and no plugins depend on it
|
* This method seems unused in core and no plugins depend on it
|
||||||
*/
|
*/
|
||||||
exports.getPadPlainText = (pad, revNum) => {
|
exports.getPadPlainText = (pad: { getInternalRevisionAText: (arg0: any) => any; atext: any; pool: any; }, revNum: undefined) => {
|
||||||
const _analyzeLine = exports._analyzeLine;
|
const _analyzeLine = exports._analyzeLine;
|
||||||
const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(checkValidRev(revNum)) : pad.atext);
|
const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(checkValidRev(revNum)) : pad.atext);
|
||||||
const textLines = atext.text.slice(0, -1).split('\n');
|
const textLines = atext.text.slice(0, -1).split('\n');
|
||||||
|
@ -47,10 +47,12 @@ exports.getPadPlainText = (pad, revNum) => {
|
||||||
|
|
||||||
return pieces.join('');
|
return pieces.join('');
|
||||||
};
|
};
|
||||||
|
type LineModel = {
|
||||||
|
[id:string]:string|number|LineModel
|
||||||
|
}
|
||||||
|
|
||||||
|
exports._analyzeLine = (text:string, aline: LineModel, apool: Function) => {
|
||||||
exports._analyzeLine = (text, aline, apool) => {
|
const line: LineModel = {};
|
||||||
const line = {};
|
|
||||||
|
|
||||||
// identify list
|
// identify list
|
||||||
let lineMarker = 0;
|
let lineMarker = 0;
|
||||||
|
@ -86,4 +88,4 @@ exports._analyzeLine = (text, aline, apool) => {
|
||||||
|
|
||||||
|
|
||||||
exports._encodeWhitespace =
|
exports._encodeWhitespace =
|
||||||
(s) => s.replace(/[^\x21-\x7E\s\t\n\r]/gu, (c) => `&#${c.codePointAt(0)};`);
|
(s:string) => s.replace(/[^\x21-\x7E\s\t\n\r]/gu, (c) => `&#${c.codePointAt(0)};`);
|
|
@ -19,13 +19,16 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {AText, PadType} from "../models/PadType";
|
||||||
|
import {MapType} from "../models/MapType";
|
||||||
|
|
||||||
const Changeset = require('../../static/js/Changeset');
|
const Changeset = require('../../static/js/Changeset');
|
||||||
const attributes = require('../../static/js/attributes');
|
const attributes = require('../../static/js/attributes');
|
||||||
const padManager = require('../db/PadManager');
|
const padManager = require('../db/PadManager');
|
||||||
const _analyzeLine = require('./ExportHelper')._analyzeLine;
|
const _analyzeLine = require('./ExportHelper')._analyzeLine;
|
||||||
|
|
||||||
// This is slightly different than the HTML method as it passes the output to getTXTFromAText
|
// This is slightly different than the HTML method as it passes the output to getTXTFromAText
|
||||||
const getPadTXT = async (pad, revNum) => {
|
const getPadTXT = async (pad: PadType, revNum: string) => {
|
||||||
let atext = pad.atext;
|
let atext = pad.atext;
|
||||||
|
|
||||||
if (revNum !== undefined) {
|
if (revNum !== undefined) {
|
||||||
|
@ -39,13 +42,13 @@ const getPadTXT = async (pad, revNum) => {
|
||||||
|
|
||||||
// This is different than the functionality provided in ExportHtml as it provides formatting
|
// This is different than the functionality provided in ExportHtml as it provides formatting
|
||||||
// functionality that is designed specifically for TXT exports
|
// functionality that is designed specifically for TXT exports
|
||||||
const getTXTFromAtext = (pad, atext, authorColors) => {
|
const getTXTFromAtext = (pad: PadType, atext: AText, authorColors?:string) => {
|
||||||
const apool = pad.apool();
|
const apool = pad.apool();
|
||||||
const textLines = atext.text.slice(0, -1).split('\n');
|
const textLines = atext.text.slice(0, -1).split('\n');
|
||||||
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||||
|
|
||||||
const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
|
const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
|
||||||
const anumMap = {};
|
const anumMap: MapType = {};
|
||||||
const css = '';
|
const css = '';
|
||||||
|
|
||||||
props.forEach((propName, i) => {
|
props.forEach((propName, i) => {
|
||||||
|
@ -55,8 +58,8 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const getLineTXT = (text, attribs) => {
|
const getLineTXT = (text:string, attribs:any) => {
|
||||||
const propVals = [false, false, false];
|
const propVals:(number|boolean)[] = [false, false, false];
|
||||||
const ENTER = 1;
|
const ENTER = 1;
|
||||||
const STAY = 2;
|
const STAY = 2;
|
||||||
const LEAVE = 0;
|
const LEAVE = 0;
|
||||||
|
@ -71,7 +74,7 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
|
||||||
|
|
||||||
let idx = 0;
|
let idx = 0;
|
||||||
|
|
||||||
const processNextChars = (numChars) => {
|
const processNextChars = (numChars: number) => {
|
||||||
if (numChars <= 0) {
|
if (numChars <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -84,7 +87,7 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
|
||||||
|
|
||||||
for (const a of attributes.decodeAttribString(o.attribs)) {
|
for (const a of attributes.decodeAttribString(o.attribs)) {
|
||||||
if (a in anumMap) {
|
if (a in anumMap) {
|
||||||
const i = anumMap[a]; // i = 0 => bold, etc.
|
const i = anumMap[a] as number; // i = 0 => bold, etc.
|
||||||
|
|
||||||
if (!propVals[i]) {
|
if (!propVals[i]) {
|
||||||
propVals[i] = ENTER;
|
propVals[i] = ENTER;
|
||||||
|
@ -189,7 +192,7 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
|
||||||
// want to deal gracefully with blank lines.
|
// want to deal gracefully with blank lines.
|
||||||
// => keeps track of the parents level of indentation
|
// => keeps track of the parents level of indentation
|
||||||
|
|
||||||
const listNumbers = {};
|
const listNumbers:MapType = {};
|
||||||
let prevListLevel;
|
let prevListLevel;
|
||||||
|
|
||||||
for (let i = 0; i < textLines.length; i++) {
|
for (let i = 0; i < textLines.length; i++) {
|
||||||
|
@ -233,6 +236,7 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
|
||||||
delete listNumbers[prevListLevel];
|
delete listNumbers[prevListLevel];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
listNumbers[line.listLevel]++;
|
listNumbers[line.listLevel]++;
|
||||||
if (line.listLevel > 1) {
|
if (line.listLevel > 1) {
|
||||||
let x = 1;
|
let x = 1;
|
||||||
|
@ -258,7 +262,7 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
|
||||||
|
|
||||||
exports.getTXTFromAtext = getTXTFromAtext;
|
exports.getTXTFromAtext = getTXTFromAtext;
|
||||||
|
|
||||||
exports.getPadTXTDocument = async (padId, revNum) => {
|
exports.getPadTXTDocument = async (padId:string, revNum:string) => {
|
||||||
const pad = await padManager.getPad(padId);
|
const pad = await padManager.getPad(padId);
|
||||||
return getPadTXT(pad, revNum);
|
return getPadTXT(pad, revNum);
|
||||||
};
|
};
|
|
@ -4,7 +4,7 @@ const CustomError = require('../utils/customError');
|
||||||
|
|
||||||
// checks if a rev is a legal number
|
// checks if a rev is a legal number
|
||||||
// pre-condition is that `rev` is not undefined
|
// pre-condition is that `rev` is not undefined
|
||||||
const checkValidRev = (rev) => {
|
const checkValidRev = (rev: number|string) => {
|
||||||
if (typeof rev !== 'number') {
|
if (typeof rev !== 'number') {
|
||||||
rev = parseInt(rev, 10);
|
rev = parseInt(rev, 10);
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ const checkValidRev = (rev) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// checks if a number is an int
|
// checks if a number is an int
|
||||||
const isInt = (value) => (parseFloat(value) === parseInt(value, 10)) && !isNaN(value);
|
const isInt = (value:number) => (parseFloat(String(value)) === parseInt(String(value), 10)) && !isNaN(value);
|
||||||
|
|
||||||
exports.isInt = isInt;
|
exports.isInt = isInt;
|
||||||
exports.checkValidRev = checkValidRev;
|
exports.checkValidRev = checkValidRev;
|
|
@ -10,11 +10,11 @@
|
||||||
class CustomError extends Error {
|
class CustomError extends Error {
|
||||||
/**
|
/**
|
||||||
* Creates an instance of CustomError.
|
* Creates an instance of CustomError.
|
||||||
* @param {*} message
|
* @param {string} message
|
||||||
* @param {string} [name='Error'] a custom name for the error object
|
* @param {string} [name='Error'] a custom name for the error object
|
||||||
* @memberof CustomError
|
* @memberof CustomError
|
||||||
*/
|
*/
|
||||||
constructor(message, name = 'Error') {
|
constructor(message:string, name: string = 'Error') {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = name;
|
this.name = name;
|
||||||
Error.captureStackTrace(this, this.constructor);
|
Error.captureStackTrace(this, this.constructor);
|
|
@ -1,8 +1,8 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
const check = (path) => {
|
const check = (path:string) => {
|
||||||
const existsSync = fs.statSync || fs.existsSync || path.existsSync;
|
const existsSync = fs.statSync || fs.existsSync;
|
||||||
|
|
||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
Loading…
Add table
Add a link
Reference in a new issue