add all services and set version numbers AIM 3.x expects

This commit is contained in:
Artem Titoulenko 2021-09-11 13:03:00 -04:00
parent b699ef0ba0
commit 0e42714b4c
22 changed files with 593 additions and 49 deletions

View file

@ -4,7 +4,8 @@
"main": "src/index.js",
"license": "MIT",
"scripts": {
"dev": "nodemon --ignore ./dist/ --watch ./src -e ts --exec 'tsc && node ./dist/index.js'",
"dev:tsc": "tsc --watch",
"dev:nodemon": "nodemon --watch ./dist ./dist/index.js",
"start": "tsc && node ./dist/index.js"
},
"devDependencies": {
@ -12,5 +13,8 @@
"@types/node": "^16.7.13",
"nodemon": "^2.0.12",
"typescript": "^4.4.2"
},
"dependencies": {
"table-layout": "^3.0.0"
}
}

View file

@ -3,13 +3,29 @@ import { FLAP, SNAC, TLV, TLVType } from './structures';
import { logDataStream } from './util';
import { FLAGS_EMPTY } from './consts';
import AuthorizationRegistrationService from "./services/authorization-registration";
import GenericServiceControls from "./services/0x01-GenericServiceControls";
import LocationServices from "./services/0x02-LocationSerices";
import BuddyListManagement from "./services/0x03-BuddyListManagement";
import ICBM from "./services/0x04-ICBM";
import Invitation from "./services/0x06-Invitation";
import Administration from "./services/0x07-Administration";
import Popups from "./services/0x08-Popups";
import PrivacyManagement from "./services/0x09-PrivacyManagement";
import UserLookup from "./services/0x0a-UserLookup";
import UsageStats from "./services/0x0b-UsageStats";
import ChatNavigation from "./services/0x0d-ChatNavigation";
import Chat from "./services/0x0e-Chat";;
import DirectorySearch from "./services/0x0f-DirectorySearch";
import ServerStoredBuddyIcons from "./services/0x10-ServerStoredBuddyIcons";
import SSI from "./services/0x13-SSI";
import AuthorizationRegistrationService from "./services/0x17-AuthorizationRegistration";
import BaseService from "./services/base";
export default class Communicator {
private _sequenceNumber = 0;
private services : {[key: number]: BaseService} = {};
public services : {[key: number]: BaseService} = {};
constructor(public socket : net.Socket) {
// Hold on to the socket
@ -17,9 +33,9 @@ export default class Communicator {
this.socket.on('data', (data : Buffer) => {
console.log('DATA-----------------------');
console.log('RAW\n' + logDataStream(data));
const flap = FLAP.fromBuffer(data);
console.log('RECV', flap.toString());
console.log('RAW\n' + logDataStream(data));
this.handleMessage(flap);
});
@ -29,7 +45,6 @@ export default class Communicator {
start() {
// Start negotiating a connection
console.log(FLAP, typeof FLAP);
const hello = new FLAP(0x01, 0, Buffer.from([0x00, 0x00, 0x00, 0x01]));
this.send(hello);
}
@ -37,6 +52,21 @@ export default class Communicator {
registerServices() {
const services = [
new AuthorizationRegistrationService(this),
new GenericServiceControls(this),
new LocationServices(this),
new BuddyListManagement(this),
new ICBM(this),
new Invitation(this),
new Administration(this),
new Popups(this),
new PrivacyManagement(this),
new UserLookup(this),
new UsageStats(this),
new ChatNavigation(this),
new Chat(this),
new DirectorySearch(this),
new ServerStoredBuddyIcons(this),
new SSI(this),
];
this.services = {};
@ -86,9 +116,7 @@ export default class Communicator {
servicesOffered.push(Buffer.from([0x00, service.family]));
});
const resp = new FLAP(2, this._getNewSequenceNumber(),
new SNAC(0x01, 0x03, FLAGS_EMPTY, 0, [
Buffer.concat(servicesOffered),
]));
new SNAC(0x01, 0x03, FLAGS_EMPTY, 0, Buffer.concat(servicesOffered)));
this.send(resp);
return;
}
@ -107,6 +135,7 @@ export default class Communicator {
}
familyService.handleMessage(message);
return;
default:
console.warn('No handlers for channel', message.channel);
return;

View file

@ -0,0 +1,40 @@
import BaseService from './base';
import Communicator from '../communicator';
import { FLAP, Rate, RateClass, RatedServiceGroup, RateGroupPair, SNAC } from '../structures';
import { FLAGS_EMPTY } from '../consts';
export default class GenericServiceControls extends BaseService {
constructor(communicator : Communicator) {
super({family: 0x01, version: 0x03}, communicator)
}
override handleMessage(message : FLAP) {
if (!(message.payload instanceof SNAC)) {
throw new Error('Require SNAC');
}
if (message.payload.service === 0x06) { // Client ask server for rate limits info
const resp = new FLAP(0x02, this._getNewSequenceNumber(),
SNAC.forRateClass(0x01, 0x07, FLAGS_EMPTY, 0, [
new Rate(
new RateClass(1, 80, 2500, 2000, 1500, 800, 3400 /*fake*/, 6000, 0, 0),
new RatedServiceGroup(1, [new RateGroupPair(0x00, 0x00)])
)
]))
this.send(resp);
return;
}
if (message.payload.service === 0x17) {
const serviceVersions : Buffer[] = [];
Object.values(this.communicator.services).forEach((service) => {
serviceVersions.push(Buffer.from([0x00, service.family, 0x00, service.version]));
});
const resp = new FLAP(0x02, this._getNewSequenceNumber(),
new SNAC(0x01, 0x18, FLAGS_EMPTY, 0, Buffer.concat(serviceVersions)));
this.send(resp);
return;
}
}
}

View file

@ -0,0 +1,8 @@
import BaseService from './base';
import Communicator from '../communicator';
export default class LocationServices extends BaseService {
constructor(communicator : Communicator) {
super({family: 0x02, version: 0x01}, communicator)
}
}

View file

@ -0,0 +1,8 @@
import BaseService from './base';
import Communicator from '../communicator';
export default class BuddyListManagement extends BaseService {
constructor(communicator : Communicator) {
super({family: 0x03, version: 0x01}, communicator)
}
}

View file

@ -0,0 +1,8 @@
import BaseService from './base';
import Communicator from '../communicator';
export default class ICBM extends BaseService {
constructor(communicator : Communicator) {
super({family: 0x04, version: 0x01}, communicator)
}
}

View file

@ -0,0 +1,8 @@
import BaseService from './base';
import Communicator from '../communicator';
export default class Invitation extends BaseService {
constructor(communicator : Communicator) {
super({family: 0x06, version: 0x01}, communicator)
}
}

View file

@ -0,0 +1,8 @@
import BaseService from './base';
import Communicator from '../communicator';
export default class Administration extends BaseService {
constructor(communicator : Communicator) {
super({family: 0x07, version: 0x01}, communicator)
}
}

View file

@ -0,0 +1,8 @@
import BaseService from './base';
import Communicator from '../communicator';
export default class Popups extends BaseService {
constructor(communicator : Communicator) {
super({family: 0x08, version: 0x01}, communicator)
}
}

View file

@ -0,0 +1,8 @@
import BaseService from './base';
import Communicator from '../communicator';
export default class PrivacyManagement extends BaseService {
constructor(communicator : Communicator) {
super({family: 0x09, version: 0x01}, communicator)
}
}

View file

@ -0,0 +1,8 @@
import BaseService from './base';
import Communicator from '../communicator';
export default class UserLookup extends BaseService {
constructor(communicator : Communicator) {
super({family: 0x0a, version: 0x01}, communicator)
}
}

View file

@ -0,0 +1,8 @@
import BaseService from './base';
import Communicator from '../communicator';
export default class UsageStats extends BaseService {
constructor(communicator : Communicator) {
super({family: 0x0b, version: 0x01}, communicator)
}
}

View file

@ -0,0 +1,8 @@
import BaseService from './base';
import Communicator from '../communicator';
export default class ChatNavigation extends BaseService {
constructor(communicator : Communicator) {
super({family: 0x0d, version: 0x02}, communicator)
}
}

View file

@ -0,0 +1,8 @@
import BaseService from './base';
import Communicator from '../communicator';
export default class Chat extends BaseService {
constructor(communicator : Communicator) {
super({family: 0x0e, version: 0x01}, communicator)
}
}

View file

@ -0,0 +1,8 @@
import BaseService from './base';
import Communicator from '../communicator';
export default class DirectorySearch extends BaseService {
constructor(communicator : Communicator) {
super({family: 0x0f, version: 0x01}, communicator)
}
}

View file

@ -0,0 +1,8 @@
import BaseService from './base';
import Communicator from '../communicator';
export default class ServerStoredBuddyIcons extends BaseService {
constructor(communicator : Communicator) {
super({family: 0x10, version: 0x01}, communicator)
}
}

9
src/services/0x13-SSI.ts Normal file
View file

@ -0,0 +1,9 @@
import BaseService from './base';
import Communicator from '../communicator';
// SSI is Server Stored Information
export default class SSI extends BaseService {
constructor(communicator : Communicator) {
super({family: 0x10, version: 0x01}, communicator)
}
}

View file

@ -25,14 +25,14 @@ export default class AuthorizationRegistrationService extends BaseService {
switch (message.payload.service) {
case 0x02: // Client login request (md5 login sequence)
const tlvs = message.payload.tlvs;
const clientNameTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === TLVType.ClientName);
const payload = message.payload.payload;
const clientNameTLV = payload.find((tlv) => tlv instanceof TLV && tlv.type === TLVType.ClientName);
if (!clientNameTLV || !(clientNameTLV instanceof TLV)) {
return;
}
console.log("Attempting connection from", clientNameTLV.payload.toString('ascii'));
const userTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === TLVType.User);
const userTLV = payload.find((tlv) => tlv instanceof TLV && tlv.type === TLVType.User);
if (!userTLV || !(userTLV instanceof TLV)) {
return;
}
@ -50,7 +50,7 @@ export default class AuthorizationRegistrationService extends BaseService {
return;
}
const passwordHashTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === TLVType.PasswordHash);
const passwordHashTLV = payload.find((tlv) => tlv instanceof TLV && tlv.type === TLVType.PasswordHash);
if (!passwordHashTLV || !(passwordHashTLV instanceof TLV)) {
return;
}
@ -76,18 +76,18 @@ export default class AuthorizationRegistrationService extends BaseService {
new SNAC(0x17, 0x03, FLAGS_EMPTY, 0, [
TLV.forUsername(username), // username
TLV.forBOSAddress('10.0.1.29:5190'), // BOS address
TLV.forCookie('im a cookie uwu') // Authorization cookie
TLV.forCookie(JSON.stringify({cookie: 'uwu', user: 'toof'})) // Authorization cookie
]));
this.send(authResp);
return;
case 0x06: // Request md5 authkey
const payload = Buffer.alloc(2, 0xFF, 'hex');
payload.writeUInt16BE(this.cipher.length);
const MD5AuthKeyHeader = Buffer.alloc(2, 0xFF, 'hex');
MD5AuthKeyHeader.writeUInt16BE(this.cipher.length);
const md5ReqResp = new FLAP(2, this._getNewSequenceNumber(),
new SNAC(0x17, 0x07, FLAGS_EMPTY, 0, [
Buffer.concat([payload, Buffer.from(this.cipher, 'binary')]),
]));
new SNAC(0x17, 0x07, FLAGS_EMPTY, 0,
Buffer.concat([MD5AuthKeyHeader, Buffer.from(this.cipher, 'binary')]),
));
this.send(md5ReqResp);
break;
}

View file

@ -25,6 +25,7 @@ export default class BaseService {
}
handleMessage(message : FLAP) : void {
return;
throw new Error(''+
`Unhandled message for family ${this.family.toString(16)} supporting version ${this.version.toString(16)}`);
}
}

View file

@ -1,41 +1,120 @@
import assert from "assert";
import { TLV } from "./TLV";
export class RateClass {
constructor(
public ID: number,
public WindowSize: number,
public ClearLevel: number,
public AlertLevel: number,
public LimitLevel: number,
public DisconnectLevel: number,
public CurrentLevel: number,
public MaxLevel: number,
public LastTime: number,
public CurrentStat: number,
){}
toBuffer() : Buffer {
const buf = Buffer.alloc(35, 0x00);
buf.writeUInt16BE(this.ID, 0);
buf.writeUInt32BE(this.WindowSize, 2);
buf.writeUInt32BE(this.ClearLevel, 6);
buf.writeUInt32BE(this.AlertLevel, 10);
buf.writeUInt32BE(this.LimitLevel, 14);
buf.writeUInt32BE(this.DisconnectLevel,18);
buf.writeUInt32BE(this.CurrentLevel, 22);
buf.writeUInt32BE(this.MaxLevel, 26);
buf.writeUInt32BE(this.LastTime, 30);
buf.writeUInt8(this.CurrentStat, 34);
return buf;
}
}
export class RateGroupPair {
constructor(public family : number, public service : number) {}
toBuffer() : Buffer {
const buf = Buffer.alloc(4, 0x00);
buf.writeInt16BE(this.family, 0);
buf.writeInt16BE(this.service, 2);
return buf;
}
}
export class RatedServiceGroup {
constructor(public rateGroupID : number, public pairs : RateGroupPair[]){}
toBuffer() : Buffer {
const ratedServiceGroupHeader = Buffer.alloc(4, 0x00);
ratedServiceGroupHeader.writeInt16BE(this.rateGroupID);
ratedServiceGroupHeader.writeInt16BE(this.pairs.length, 2);
const pairs = this.pairs.map((pair) => pair.toBuffer());
return Buffer.concat([ratedServiceGroupHeader, ...pairs]);
}
}
export class Rate {
constructor(public rateClass : RateClass, public ratedServiceGroup : RatedServiceGroup) {}
toBuffer() : Buffer {
return Buffer.concat([this.rateClass.toBuffer(), this.ratedServiceGroup.toBuffer()]);
}
}
export class SNAC {
constructor(public family : number, public service : number, public flags : Buffer, public requestID : number , public payload : (TLV[] | Buffer) = Buffer.alloc(0)) {
this.family = family;
this.service = service;
this.flags = flags;
this.requestID = requestID;
this.payload = payload;
}
static fromBuffer(buf : Buffer, payloadLength = 0) {
assert(buf.length >= 10, 'Expected 10 bytes for SNAC header');
const family = buf.slice(0,2).readInt16BE(0);
const service = buf.slice(2,4).readInt16BE(0);
const flags = buf.slice(4, 6);
const requestID = buf.slice(6, 10).readInt32BE(0);
const tlvs : TLV[] = []; // SNACs can have multiple TLVs
let payload : Buffer | TLV[]; // SNACs can have multiple payload
let tlvsIdx = 10;
let cb = 0, cbLimit = 20; //circuit breaker
while (tlvsIdx < payloadLength && cb < cbLimit) {
const tlv = TLV.fromBuffer(buf.slice(tlvsIdx));
tlvs.push(tlv);
tlvsIdx += tlv.length + 4; // 4 bytes for TLV type + tlvs length
cb++;
// Some SNACs don't have TLV payloads
if (family === 0x01 && service === 0x17 ||
family === 0x01 && service === 0x07 ||
family === 0x01 && service === 0x08 ||
family === 0x01 && service === 0x0e) {
payload = buf.slice(10, 10 + payloadLength);
} else {
payload = [];
// Try to parse TLVs
let payloadIdx = 10;
let cb = 0, cbLimit = 20; //circuit breaker
while (payloadIdx < payloadLength && cb < cbLimit) {
const tlv = TLV.fromBuffer(buf.slice(payloadIdx));
payload.push(tlv);
payloadIdx += tlv.length + 4; // 4 bytes for TLV type + payload length
cb++;
}
if (cb === cbLimit) {
console.error('Application error, cb limit reached');
process.exit(1);
}
}
if (cb === cbLimit) {
console.error('Application error, cb limit reached');
process.exit(1);
}
return new SNAC(family, service, flags, requestID, tlvs);
return new SNAC(family, service, flags, requestID, payload);
}
constructor(public family : number, public service : number, public flags : Buffer, public requestID : number , public tlvs : Array<TLV | Buffer> = []) {
this.family = family;
this.service = service;
this.flags = flags;
this.requestID = requestID;
this.tlvs = tlvs;
static forRateClass(family : number, service : number, flags : Buffer, requestID : number, rates : Rate[]) : SNAC {
const payloadHeader = Buffer.alloc(2, 0x00);
payloadHeader.writeUInt16BE(rates.length);
const payloadBody = rates.map((rateClass) => rateClass.toBuffer());
const payload = Buffer.concat([payloadHeader, ...payloadBody]);
return new SNAC(family, service, flags, requestID, payload);
}
toString() {
return `SNAC(${this.family.toString(16)},${this.service.toString(16)}) #${this.requestID}\n ${this.tlvs}`;
return `SNAC(${this.family.toString(16)},${this.service.toString(16)}) #${this.requestID}\n ${this.payload}`;
}
toBuffer() {
@ -45,12 +124,12 @@ export class SNAC {
SNACHeader.set(this.flags, 4);
SNACHeader.writeUInt32BE(this.requestID, 6);
const payload = this.tlvs.map((thing) => {
if (thing instanceof TLV) {
return thing.toBuffer();
}
return thing;
});
let payload : Buffer[] = [];
if (this.payload instanceof Buffer) {
payload = [this.payload];
} else if (this.payload.length && this.payload[0] instanceof TLV) {
payload = (this.payload as TLV[]).map((thing : TLV) => thing.toBuffer());
}
return Buffer.concat([SNACHeader, ...payload]);
}

View file

@ -1,4 +1,5 @@
function chunkString(str : string, len : number) {
import { FLAP } from './structures'
export function chunkString(str : string, len : number) {
const size = Math.ceil(str.length/len)
const r = Array(size)
let offset = 0
@ -15,3 +16,126 @@ export function logDataStream(data : Buffer){
const strs = chunkString(data.toString('hex'), 16);
return strs.map((str) => chunkString(str, 2).join(' ')).join('\n');
}
// Experiment to provide descriptive print-outs of the data structures
// @ts-ignore
import Table from 'table-layout';
type DataSize = -1 | 8 | 16 | 32;
interface Spec {
description: string,
size : DataSize,
isRepeat? : boolean,
repeatSpecs?: Spec[],
}
function byte(description : string) : Spec {
return {size: 8, description}
}
function word(description : string) : Spec {
return {size: 16, description};
}
function dword(description : string) : Spec {
return {size: 32, description};
}
function repeat(size: DataSize, description : string, specs : Spec[]) : Spec {
return {size, description, isRepeat: true, repeatSpecs: specs};
}
const FLAPSpec = [
byte("FLAP Header"),
byte("Channel"),
word("Sequence ID"),
word("Payload Length"),
word("SNAC Family"),
word("SNAC Subtype"),
word("SNAC Flags"),
dword("SNAC Request-ID"),
repeat(16, "Number of rate classes", [
word("Rate Class ID"),
dword("Window Size"),
dword("Clear Level"),
dword("Alert Level"),
dword("Limit Level"),
dword("Disconnect Level"),
dword("Current Level"),
dword("Max Level"),
dword("Last Time"),
byte("Current State")
]),
repeat(-1, "", [
word("Rate Group ID"),
repeat(16, "Number of pairs in group", [
dword("Family/Subtype pair"),
]),
]),
];
function parseBuffer(buf : Buffer, spec : Spec[], repeatTimes = 0) {
let offset = 0;
let rows = [];
let repeat = repeatTimes;
for (let section of spec) {
let value : number = 0;
if (section.size === 8) {
const bufStr = buf.slice(offset, offset + 1).toString('hex');
value = buf.readInt8(offset);
rows.push([chunkString(bufStr, 2).join(' '), value, section.description]);
offset += 1;
} else if (section.size === 16) {
const bufStr = buf.slice(offset, offset + 2).toString('hex');
value = buf.readUInt16BE(offset);
rows.push([chunkString(bufStr, 2).join(' '), value, section.description]);
offset += 2;
} else if (section.size === 32) {
const bufStr = buf.slice(offset, offset + 4).toString('hex');
value = buf.readUInt32BE(offset);
rows.push([chunkString(bufStr, 2).join(' '), value, section.description]);
offset += 4;
}
if (section.isRepeat && section.repeatSpecs) {
if (section.size !== -1) {
repeat = value;
}
let specs : Spec[] = [];
for (let i = 0; i < repeat; i++) {
specs.push(...section.repeatSpecs);
}
const subrows : any[] = parseBuffer(buf.slice(offset), specs, repeat);
rows.push(...subrows);
}
}
return rows;
}
function printBuffer(buf : Buffer, spec : Spec[]) {
const rows = parseBuffer(buf, spec);
console.log((new Table(rows)).toString());
}
function bufferFromWebText(webtext : string) : Buffer {
return Buffer.from(webtext.replace(/\s/g, ''), 'hex');
}
const exampleWebText = ''+
`
2a 02 00 03 00 37 00 01
00 07 00 00 00 00 00 00
00 01 00 01 00 00 00 50
00 00 09 c4 00 00 07 d0
00 00 05 dc 00 00 03 20
00 00 0d 48 00 00 17 70
00 00 00 00 00 00 01 00
01 00 00 00 00 `
if (require.main === module) {
printBuffer(bufferFromWebText(exampleWebText), FLAPSpec);
}

158
yarn.lock
View file

@ -2,6 +2,14 @@
# yarn lockfile v1
"@75lb/deep-merge@^1.1.0":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@75lb/deep-merge/-/deep-merge-1.1.1.tgz#3b06155b90d34f5f8cc2107d796f1853ba02fd6d"
integrity sha512-xvgv6pkMGBA6GwdyJbNAnDmfAIR/DfWhrj9jgWh3TY7gRm3KO46x/GPjRg6wJ0nOepwqrNxFfojebh0Df4h4Tw==
dependencies:
lodash.assignwith "^4.2.0"
typical "^7.1.1"
"@sindresorhus/is@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
@ -46,6 +54,13 @@ ansi-regex@^5.0.0:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
color-convert "^1.9.0"
ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
@ -61,6 +76,21 @@ anymatch@~3.1.2:
normalize-path "^3.0.0"
picomatch "^2.0.4"
array-back@^3.0.1, array-back@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0"
integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==
array-back@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e"
integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==
array-back@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/array-back/-/array-back-6.2.0.tgz#83cc80fbef5a46269b1f6ecc82011cfc19cf1c1e"
integrity sha512-mixVv03GOOn/ubHE4STQ+uevX42ETdk0JoMVEjNkSOCT7WgERh7C8/+NyhWYNpE3BN69pxFyJIBcF7CxWz/+4A==
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@ -118,6 +148,15 @@ camelcase@^5.3.1:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
@ -158,6 +197,13 @@ clone-response@^1.0.2:
dependencies:
mimic-response "^1.0.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
@ -165,11 +211,36 @@ color-convert@^2.0.1:
dependencies:
color-name "~1.1.4"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
command-line-args@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.0.tgz#087b02748272169741f1fd7c785b295df079b9be"
integrity sha512-4zqtU1hYsSJzcJBOcNZIbW5Fbk9BkjCp1pZVhQKoRaWL5J7N4XphDLwo8aWwdQpTugxwu+jf9u2ZhkXiqp5Z6A==
dependencies:
array-back "^3.1.0"
find-replace "^3.0.0"
lodash.camelcase "^4.3.0"
typical "^4.0.0"
command-line-usage@^6.1.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/command-line-usage/-/command-line-usage-6.1.1.tgz#c908e28686108917758a49f45efb4f02f76bc03f"
integrity sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA==
dependencies:
array-back "^4.0.1"
chalk "^2.4.2"
table-layout "^1.0.1"
typical "^5.2.0"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@ -213,7 +284,7 @@ decompress-response@^3.3.0:
dependencies:
mimic-response "^1.0.0"
deep-extend@^0.6.0:
deep-extend@^0.6.0, deep-extend@~0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
@ -257,6 +328,11 @@ escape-goat@^2.0.0:
resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
@ -264,6 +340,13 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"
find-replace@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38"
integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==
dependencies:
array-back "^3.0.1"
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
@ -457,6 +540,16 @@ latest-version@^5.0.0:
dependencies:
package-json "^6.3.0"
lodash.assignwith@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz#127a97f02adc41751a954d24b0de17e100e038eb"
integrity sha1-EnqX8CrcQXUalU0ksN4X4QDgOOs=
lodash.camelcase@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
@ -603,6 +696,11 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
reduce-flatten@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27"
integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==
registry-auth-token@^4.0.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250"
@ -646,6 +744,11 @@ signal-exit@^3.0.2:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
stream-read-all@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/stream-read-all/-/stream-read-all-3.0.1.tgz#60762ae45e61d93ba0978cda7f3913790052ad96"
integrity sha512-EWZT9XOceBPlVJRrYcykW8jyRSZYbkb/0ZK36uLEmoWVO5gxBOnntNTseNzfREsqxqdfEGQrD8SXQ3QWbBmq8A==
string-width@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
@ -683,7 +786,7 @@ strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
supports-color@^5.5.0:
supports-color@^5.3.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
@ -697,6 +800,29 @@ supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
table-layout@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04"
integrity sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==
dependencies:
array-back "^4.0.1"
deep-extend "~0.6.0"
typical "^5.2.0"
wordwrapjs "^4.0.0"
table-layout@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/table-layout/-/table-layout-3.0.0.tgz#bd0f207ff73eb4dba79019e4f672e61874181b7f"
integrity sha512-chMCAvqzsw2k0WVZLQwyiNfHV3lwLdrmTKaofEt3PhedSVdEwOK8/nGetZFaQEg9HjPL9CrhkJWc4ZauFCihXw==
dependencies:
"@75lb/deep-merge" "^1.1.0"
array-back "^6.2.0"
command-line-args "^5.2.0"
command-line-usage "^6.1.1"
stream-read-all "^3.0.1"
typical "^7.1.1"
wordwrapjs "^5.1.0"
term-size@^2.1.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54"
@ -738,6 +864,21 @@ typescript@^4.4.2:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86"
integrity sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==
typical@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4"
integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==
typical@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066"
integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==
typical@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/typical/-/typical-7.1.1.tgz#ba177ab7ab103b78534463ffa4c0c9754523ac1f"
integrity sha512-T+tKVNs6Wu7IWiAce5BgMd7OZfNYUndHwc5MknN+UHOudi7sGZzuHdCadllRuqJ3fPtgFtIH9+lt9qRv6lmpfA==
undefsafe@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae"
@ -785,6 +926,19 @@ widest-line@^3.1.0:
dependencies:
string-width "^4.0.0"
wordwrapjs@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.1.tgz#d9790bccfb110a0fc7836b5ebce0937b37a8b98f"
integrity sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==
dependencies:
reduce-flatten "^2.0.0"
typical "^5.2.0"
wordwrapjs@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-5.1.0.tgz#4c4d20446dcc670b14fa115ef4f8fd9947af2b3a"
integrity sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg==
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"