remove TypeScript code, pedal to the gopher metal

This commit is contained in:
Artem Titoulenko 2021-12-19 02:26:27 -05:00
parent cca7d63e20
commit 14d363ba87
33 changed files with 6 additions and 1860 deletions

View file

@ -1,22 +1,12 @@
{ {
"name": "aim-oscar-server", "name": "aim-oscar-server",
"version": "1.0.0", "version": "1.0.0",
"main": "dist/src/index.js", "license": "GPLv4",
"license": "MIT",
"scripts": { "scripts": {
"dev:tsc": "tsc --watch", "dev:": "nodemon --watch ./ -e go --ignore '*_test.go' --delay 200ms --exec 'go build && ./aim-oscar || exit 1' --signal SIGTERM",
"dev:nodemon:auth-go": "nodemon --watch ./ -e go --ignore '*_test.go' --delay 200ms --exec 'go build && ./aim-oscar || exit 1' --signal SIGTERM", "start": "go build && ./aim-oscar"
"dev:nodemon:auth": "nodemon --watch ./dist --delay 200ms dist/src/main-auth.js",
"dev:nodemon:chat": "nodemon --watch ./dist --delay 200ms dist/src/main-chat.js",
"start": "tsc && node ./dist/index.js"
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/node14": "^1.0.1", "nodemon": "^2.0.12"
"@types/node": "^16.7.13",
"nodemon": "^2.0.12",
"typescript": "^4.4.2"
},
"dependencies": {
"table-layout": "^3.0.0"
} }
} }

View file

@ -1,142 +0,0 @@
import net from "net";
import { FLAP, SNAC, TLV, TLVType } from './structures';
import { logDataStream } from './util';
import BaseService from "./services/base";
export interface User {
uin: string,
username: string,
password: string,
memberSince: Date,
}
export default class Communicator {
private keepaliveInterval? : NodeJS.Timer;
private _sequenceNumber = 0;
private messageBuffer = Buffer.alloc(0);
public services : {[key: number]: BaseService} = {};
public user? : User;
constructor(public socket : net.Socket) {}
startListening() {
this.socket.on('data', (data : Buffer) => {
// we could get multiple FLAP messages, keep a running buffer of incoming
// data and shift-off however many successful FLAPs we can make
this.messageBuffer = Buffer.concat([this.messageBuffer, data]);
while (this.messageBuffer.length > 0) {
try {
const flap = FLAP.fromBuffer(this.messageBuffer);
console.log('DATA-----------------------');
console.log('RAW\n' + logDataStream(flap.toBuffer()));
console.log('RECV', flap.toString());
this.messageBuffer = this.messageBuffer.slice(flap.length);
this.handleMessage(flap);
console.log('-----------------------DATA');
} catch (e) {
console.error("Error handling message:", e);
}
}
});
this.keepaliveInterval = setInterval(() => {
const keepaliveFlap = new FLAP(0x05, this.nextReqID, Buffer.from(""));
this.socket.write(keepaliveFlap.toBuffer());
}, 4 * 60 * 1000);
this.socket.on('close', () => {
if (this.keepaliveInterval) {
clearInterval(this.keepaliveInterval);
}
});
// Start negotiating a connection
const hello = new FLAP(0x01, 0, Buffer.from([0x00, 0x00, 0x00, 0x01]));
this.send(hello);
}
registerServices(services : BaseService[] = []) {
// Make a map of the service number to the service handler
this.services = {};
services.forEach((service) => {
this.services[service.service] = service;
});
}
get nextReqID() {
return ++this._sequenceNumber & 0xFFFF;
}
send(message : FLAP) {
console.log('SEND', message.toString());
console.log('RAW\n' + logDataStream(message.toBuffer()));
this.socket.write(message.toBuffer());
}
handleMessage(message : FLAP) {
switch (message.channel) {
case 1:
// No SNACs on channel 1
if (!(message.payload instanceof Buffer)) {
return;
}
const protocol = message.payload.readUInt32BE();
if (protocol !== 1) {
console.log('Unsupported protocol:', protocol);
this.socket.end();
return;
}
if (message.payload.length <= 4) {
return;
}
const tlv = TLV.fromBuffer(message.payload.slice(4));
console.log('thing sent to channel 1:');
console.log(tlv.toString());
if (tlv.type === 0x06) {
// client sent us a cookie
const {cookie, user} = JSON.parse(tlv.payload.toString());
console.log('cookie:', cookie);
this.user = user;
}
if (tlv.type === TLVType.GetServices) { // Requesting available services
// this is just a dword list of subtype families
const servicesOffered : Buffer[] = [];
Object.values(this.services).forEach((subtype) => {
servicesOffered.push(Buffer.from([0x00, subtype.service]));
});
const resp = new FLAP(2, this.nextReqID,
new SNAC(0x01, 0x03, Buffer.concat(servicesOffered)));
this.send(resp);
return;
}
return;
case 2:
if (!(message.payload instanceof SNAC)) {
console.error('Expected SNAC payload');
return;
}
const familyService = this.services[message.payload.service];
if (!familyService) {
console.warn('no handler for service', message.payload.service);
return;
}
familyService.handleMessage(message);
return;
default:
console.warn('No handlers for channel', message.channel);
return;
}
}
}

View file

@ -1,64 +0,0 @@
export const AIM_MD5_STRING = "AOL Instant Messenger (SM)";
export const FLAGS_EMPTY = Buffer.from([0x00, 0x00, 0x00, 0x00]);
export const enum USER_STATUS_VARIOUS {
/**
* Status webaware flag
*/
WEBAWARE = 0x0001,
/**
* Status show ip flag
*/
SHOWIP = 0x0002,
/**
* User birthday flag
*/
BIRTHDAY = 0x0008,
/**
* User active webfront flag
*/
WEBFRONT = 0x0020,
/**
* Direct connection not supported
*/
DCDISABLED = 0x0100,
/**
* Direct connection upon authorization
*/
DCAUTH = 0x1000,
/**
* DC only with contact users
*/
DCCONT = 0x2000,
}
export const enum USER_STATUS {
/**
* Status is online
*/
ONLINE = 0x0000,
/**
* Status is away
*/
AWAY = 0x0001,
/**
* Status is no not disturb (DND)
*/
DND = 0x0002,
/**
* Status is not available (N/A)
*/
NA = 0x0004,
/**
* Status is occupied (BISY)
*/
OCCUPIED = 0x0010,
/**
* Status is free for chat
*/
FREE4CHAT = 0x0020,
/**
* Status is invisible
*/
INVISIBLE = 0x0100,
}

View file

@ -1,21 +0,0 @@
import net from "net";
const server = net.createServer((socket) => {
socket.setTimeout(5 * 60 * 1000); // 5 minute timeout
socket.on('timeout', () => {
console.log('socket timeout');
socket.end();
});
socket.on('end', () => {
console.log('client disconnected...');
});
});
server.on('error', (err) => {
console.error(err);
});
server.listen(9999, () => {
console.log('proxy ready');
})

View file

@ -1,38 +0,0 @@
import net from 'net';
import Communicator from './communicator';
import AuthorizationRegistrationService from "./services/0x17-AuthorizationRegistration";
const server = net.createServer((socket) => {
console.log('client connected...');
socket.setTimeout(5 * 60 * 1000); // 5 minute timeout
socket.on('error', (e) => {
console.error('socket encountered an error:', e);
socket.end();
});
socket.on('timeout', () => {
console.log('socket timeout');
socket.end();
});
socket.on('end', () => {
console.log('client disconnected...');
});
const comm = new Communicator(socket);
const services = [
new AuthorizationRegistrationService(comm),
];
comm.registerServices(services);
comm.startListening();
});
server.on('error', (err) => {
throw err;
});
server.listen(5190, () => {
console.log('AUTH ready on :5190');
});

View file

@ -1,66 +0,0 @@
import net from 'net';
import Communicator from './communicator';
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";
const server = net.createServer((socket) => {
console.log('client connected...');
socket.setTimeout(5 * 60 * 1000); // 5 minute timeout
socket.on('error', (e) => {
console.error('socket encountered an error:', e);
socket.end();
});
socket.on('timeout', () => {
console.log('socket timeout');
socket.end();
});
socket.on('end', () => {
console.log('client disconnected...');
});
const comm = new Communicator(socket);
const services = [
new GenericServiceControls(comm),
// new LocationServices(comm),
// new BuddyListManagement(comm),
new ICBM(comm),
// new Invitation(comm),
// new Administration(comm),
// new Popups(comm),
// new PrivacyManagement(comm),
// new UserLookup(comm),
// new UsageStats(comm),
// new ChatNavigation(comm),
// new Chat(comm),
// new DirectorySearch(comm),
// new ServerStoredBuddyIcons(comm),
// new SSI(comm),
];
comm.registerServices(services);
comm.startListening();
});
server.on('error', (err) => {
throw err;
});
server.listen(5191, () => {
console.log('CHAT ready on :5191');
});

View file

@ -1,117 +0,0 @@
import BaseService from './base';
import Communicator from '../communicator';
import { FLAP, Rate, RateClass, RatedServiceGroup, RateGroupPair, SNAC, TLV } from '../structures';
import { USER_STATUS_VARIOUS, USER_STATUS } from '../consts';
import { char, word, dword, dot2num } from '../structures/bytes';
export default class GenericServiceControls extends BaseService {
private allowViewIdle = false;
private allowViewMemberSince = false;
constructor(communicator : Communicator) {
super({service: 0x01, version: 0x03}, [0x06, 0x0e, 0x14, 0x17], communicator)
}
override handleMessage(message : FLAP) {
if (!(message.payload instanceof SNAC)) {
throw new Error('Require SNAC');
}
if (message.payload.subtype === 0x06) { // Client ask server for rate limits info
// HACK: set rate limits for all services. I can't tell which message subtypes they support so
// make it set rate limits for everything under 0x21.
const pairs : RateGroupPair[] = [];
Object.values(this.communicator.services).forEach((service) => {
// for (let subtype of service.supportedSubtypes) {
for (let subtype = 0; subtype < 0x21; subtype++) {
pairs.push(new RateGroupPair(service.service, subtype));
}
});
const resp = new FLAP(0x02, this.nextReqID,
SNAC.forRateClass(0x01, 0x07, [
new Rate(
new RateClass(1, 80, 2500, 2000, 1500, 800, 3400 /*fake*/, 6000, 0, 0),
new RatedServiceGroup(1, pairs),
)
]));
this.send(resp);
const motd = new FLAP(0x02, this.nextReqID,
new SNAC(0x01, 0x13, Buffer.concat([
word(0x0004),
(new TLV(0x0B, Buffer.from("Hello world!"))).toBuffer(),
])));
this.send(motd);
return;
}
if (message.payload.subtype === 0x0e) { // Client requests own online information
const uin = this.communicator.user?.username || 'user';
const warning = 0;
const since = +(new Date('December 17, 1998 03:24:00'));
const externalIP = dot2num(this.communicator.socket.remoteAddress!.split(':').pop()!);
const tlvs : TLV[] = [
new TLV(0x01, char(0x80)),
new TLV(0x06, dword(USER_STATUS_VARIOUS.WEBAWARE | USER_STATUS_VARIOUS.DCDISABLED << 2 + USER_STATUS.ONLINE)),
new TLV(0x0A, dword(externalIP)),
new TLV(0x0F, dword(0)), // TODO: track idle time,
new TLV(0x03, dword(Math.floor(Date.now() / 1000))),
new TLV(0x1E, dword(0)), // Unknown
new TLV(0x05, dword(Math.floor(since / 1000))),
new TLV(0x0C, Buffer.concat([
dword(externalIP),
dword(5700), // DC TCP Port
dword(0x04000000), // DC Type,
word(0x0400), // DC Protocol Version
dword(0), // DC Auth Cookie
dword(0), // Web Front port
dword(0x300), // Client Features ?
dword(0), // Last Info Update Time
dword(0), // last EXT info update time,
dword(0), // last ext status update time
]))
];
const payloadHeader = Buffer.alloc(1 + uin.length + 2 + 2);
payloadHeader.writeInt8(uin.length);
payloadHeader.set(Buffer.from(uin), 1);
payloadHeader.writeInt16BE(warning, 1 + uin.length);
payloadHeader.writeInt16BE(tlvs.length, 1 + uin.length + 2);
const buf = Buffer.concat([payloadHeader, ...tlvs.map((tlv) => tlv.toBuffer())])
const resp = new FLAP(0x02, this.nextReqID,
new SNAC(0x01, 0x0f, buf));
this.send(resp);
return;
}
if (message.payload.subtype === 0x14) {
/*
Client setting privacy settings
Bit 1 - Allows other AIM users to see how long you've been idle.
Bit 2 - Allows other AIM users to see how long you've been a member.
*/
const mask = (message.payload.payload as Buffer).readUInt32BE();
this.allowViewIdle = (mask & 0x01) > 0;
this.allowViewMemberSince = (mask & 0x02) > 0;
console.log('allowViewIdle:', this.allowViewIdle, 'allowViewMemberSince', this.allowViewMemberSince);
return;
}
if (message.payload.subtype === 0x17) {
const serviceVersions : Buffer[] = [];
Object.values(this.communicator.services).forEach((subtype) => {
serviceVersions.push(Buffer.from([0x00, subtype.service, 0x00, subtype.version]));
});
const resp = new FLAP(0x02, this.nextReqID,
new SNAC(0x01, 0x18, Buffer.concat(serviceVersions)));
this.send(resp);
return;
}
}
}

View file

@ -1,37 +0,0 @@
import BaseService from './base';
import Communicator from '../communicator';
import { FLAP, SNAC, TLV } from '../structures';
import { char, word, dword, dot2num } from '../structures/bytes';
import { USER_STATUS, USER_STATUS_VARIOUS } from '../consts';
export default class LocationServices extends BaseService {
constructor(communicator : Communicator) {
super({service: 0x02, version: 0x01}, [0x02, 0x04], communicator)
}
override handleMessage(message : FLAP) {
if (!(message.payload instanceof SNAC)) {
throw new Error('Expecting SNACs for LocationServices')
}
// request location service parameters and limitations
if (message.payload.subtype === 0x02) {
const resp = new FLAP(0x02, this.nextReqID,
new SNAC(0x02,0x03, [
new TLV(0x01, word(0x400)), // max profile length
new TLV(0x02, word(0x10)), // max capabilities
new TLV(0x03, word(0xA)), // unknown
new TLV(0x04, word(0x1000)),
]));
this.send(resp);
return;
}
if (message.payload.subtype === 0x04) {
// Client use this snac to set its location information (like client
// profile string, client directory profile string, client capabilities).
return;
}
}
}

View file

@ -1,30 +0,0 @@
import BaseService from './base';
import Communicator from '../communicator';
import { FLAGS_EMPTY } from '../consts';
import { FLAP, SNAC, TLV } from '../structures';
import { word } from '../structures/bytes';
export default class BuddyListManagement extends BaseService {
constructor(communicator : Communicator) {
super({service: 0x03, version: 0x01}, [0x02], communicator)
}
override handleMessage(message : FLAP) {
if (!(message.payload instanceof SNAC)) {
throw new Error('Expected SNACs')
}
if (message.payload.subtype === 0x02) {
const resp = new FLAP(0x02, this.nextReqID,
new SNAC(0x03, 0x03, [
new TLV(0x01, word(600)), // 600 max buddies
new TLV(0x02, word(750)), // 750 max watchers
new TLV(0x03, word(512)), // 512 max online notifications ?
]));
this.send(resp);
return;
}
}
}

View file

@ -1,157 +0,0 @@
import BaseService from './base';
import Communicator from '../communicator';
import { FLAGS_EMPTY } from '../consts';
import { FLAP, SNAC, TLV } from '../structures';
import { char, dword, word } from '../structures/bytes';
interface ChannelSettings {
channel: number,
messageFlags: number,
maxMessageSnacSize: number,
maxSenderWarningLevel: number,
maxReceiverWarningLevel: number,
minimumMessageInterval: number,
unknown : number
}
export default class ICBM extends BaseService {
/* Inter-Client Basic Message
This system passes messages from/to clients through the server
instead of directly between clients.
*/
private channel : ChannelSettings = {
channel: 0,
messageFlags: 3,
maxMessageSnacSize: 512,
maxSenderWarningLevel: 999,
maxReceiverWarningLevel: 999,
minimumMessageInterval: 0,
unknown: 1000,
};
private channels : ChannelSettings[] = [];
constructor(communicator : Communicator) {
super({service: 0x04, version: 0x01}, [0x02, 0x04], communicator)
}
override handleMessage(message : FLAP) {
if (!(message.payload instanceof SNAC)) {
throw new Error('Expected SNACs')
}
if (message.payload.subtype === 0x02) {
// client is telling us about it's ICBM capabilities (whatever)
/*
xx xx word channel to setup
xx xx xx xx dword message flags
xx xx word max message snac size
xx xx word max sender warning level
xx xx word max receiver warning level
xx xx word minimum message interval (sec)
00 00 word unknown parameter (also seen 03 E8)
*/
if (!(message.payload.payload instanceof Buffer)) {
throw new Error('Expected Buffer payload for this SNAC');
}
const payload = message.payload.payload;
const channel = payload.readUInt16BE(0);
// TODO: set settings based on channel provided
this.channel = {
channel,
messageFlags: payload.readUInt32BE(2),
maxMessageSnacSize: payload.readUInt16BE(6),
maxSenderWarningLevel: payload.readUInt16BE(8),
maxReceiverWarningLevel: payload.readUInt16BE(10),
minimumMessageInterval: payload.readUInt16BE(12),
unknown: 1000, //payload.readUInt16BE(14),
}
console.log("ICBM set channel", this.channel);
return;
}
if (message.payload.subtype === 0x04) {
const payload = Buffer.alloc(16, 0x00);
payload.writeInt16BE(this.channel.channel, 0);
payload.writeInt32BE(this.channel.messageFlags, 2);
payload.writeInt16BE(this.channel.maxMessageSnacSize, 6);
payload.writeInt16BE(this.channel.maxSenderWarningLevel, 8);
payload.writeInt16BE(this.channel.maxReceiverWarningLevel, 10);
payload.writeInt16BE(this.channel.minimumMessageInterval, 12);
payload.writeInt16BE(this.channel.unknown, 14);
const resp = new FLAP(0x02, this.nextReqID,
new SNAC(0x04, 0x05, payload));
this.send(resp);
return;
}
if (message.payload.subtype === 0x06) {
// Client sent us a message to deliver to another client
/*
Channel 1 is used for what would commonly be called an "instant message" (plain text messages).
Channel 2 is used for complex messages (rtf, utf8) and negotiating "rendezvous". These transactions end in something more complex happening, such as a chat invitation, or a file transfer.
Channel 3 is used for chat messages (not in the same family as these channels).
*/
if (!(message.payload.payload instanceof Buffer)) {
throw new Error('this should be a buffer');
}
const msgId = message.payload.payload.readBigUInt64BE(0);
const channel = message.payload.payload.readUInt16BE(8);
const screenNameLength = message.payload.payload.readUInt8(10);
const screenName = message.payload.payload.slice(11, 11 + screenNameLength).toString();
console.log({
msgId, channel, screenName,
});
if (channel === 1) {
const tlvs = TLV.fromBufferBlob(message.payload.payload.slice(11 + screenNameLength));
console.log(tlvs);
// does the client want us to acknowledge that we got the message?
const wantsAck = tlvs.find((tlv) => tlv.type === 3);
// lets parse the message
const messageTLV = tlvs.find((tlv) => tlv.type === 2);
if (!messageTLV) {
// TODO: send back error response
throw new Error('need a message');
}
// Start parsing the message TLV payload
// first is the array of capabilities
const startOfMessageFragment = 2 + messageTLV.payload.readUInt16BE(2);
const lengthOfMessageText = messageTLV.payload.readUInt16BE(startOfMessageFragment + 2);
const messageText = messageTLV.payload.slice(startOfMessageFragment + 8, startOfMessageFragment + 8 + lengthOfMessageText).toString();
console.log('The user said:', messageText);
// The client usually wants a response that the server got the message. It checks that the message
// back has the same message ID that was sent and the user it was sent to.
if (wantsAck) {
const sender = this.communicator.user?.username || "";
const msgIdBuffer = Buffer.alloc(32);
msgIdBuffer.writeBigUInt64BE(msgId);
const ackPayload = Buffer.from([
...msgIdBuffer,
...word(0x02),
...char(sender.length),
...Buffer.from(sender),
]);
const ackResp = new FLAP(2, this.nextReqID, new SNAC(0x04, 0x0c, ackPayload));
this.send(ackResp);
}
// TODO: find the connection that the receiver is connected to and send them the message
// this is the rest of the owl for this part
}
}
}
}

View file

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

View file

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

View file

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

View file

@ -1,38 +0,0 @@
import BaseService from './base';
import Communicator from '../communicator';
import { FLAGS_EMPTY } from '../consts';
import { FLAP, SNAC, TLV } from '../structures';
import { word } from '../structures/bytes';
export default class PrivacyManagement extends BaseService {
private permissionMask: number = 0xffff; // everyone
constructor(communicator : Communicator) {
super({service: 0x09, version: 0x01}, [0x02, 0x04], communicator)
}
override handleMessage(message : FLAP) {
if (!(message.payload instanceof SNAC)) {
throw new Error('Expected SNACs')
}
if (message.payload.subtype === 0x02) {
const resp = new FLAP(0x02, this.nextReqID,
new SNAC(0x09, 0x03, [
new TLV(0x01, word(200)), // max visible list size
new TLV(0x02, word(200)) // max invisible list size
]));
this.send(resp);
return;
}
if (message.payload.subtype === 0x04) {
// Client sends permission mask for classes of users that can talk to the client
this.permissionMask = (message.payload.payload as Buffer).readUInt32BE();
console.log('set permission mask', this.permissionMask.toString(16));
return;
}
}
}

View file

@ -1,44 +0,0 @@
import BaseService from './base';
import Communicator from '../communicator';
import { FLAP, SNAC, TLV } from '../structures';
import { word } from '../structures/bytes';
const emailToUin : {[key: string]: string[]} = {
'bob@example.com': ['bobX0X0'],
};
export default class UserLookup extends BaseService {
constructor(communicator : Communicator) {
super({service: 0x0a, version: 0x01}, [0x02], communicator)
}
override handleMessage(message: FLAP) {
if (!(message.payload instanceof SNAC)) {
throw new Error('Require SNAC');
}
// Search for a user by email address
// TODO: don't return users that don't want to be found via email
if (message.payload.subtype === 0x02) {
if (!(message.payload instanceof Buffer)) {
// 0x0e: Incorrect SNAC format
const incorrectFormatResp = new FLAP(2, this.nextReqID, new SNAC(0x0a, 0x01, word(0x0e)));
this.send(incorrectFormatResp);
return;
}
const email = message.payload.payload.toString();
if (!emailToUin[email]) {
// 0x14: No Match
const noResult = new FLAP(2, this.nextReqID, new SNAC(0x0a, 0x01, word(0x14)));
this.send(noResult);
return;
}
// Return list of TLVs of matching UINs
const results = emailToUin[email].map((uin) => new TLV(0x01, Buffer.from(uin)))
const resp = new FLAP(2, this.nextReqID, new SNAC(0x0a, 0x03, results));
this.send(resp);
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,117 +0,0 @@
import crypto from 'crypto';
import BaseService from './base';
import Communicator, { User } from '../communicator';
import { FLAP, SNAC, TLV, ErrorCode, TLVType } from '../structures';
import { word } from '../structures/bytes';
const { AIM_MD5_STRING, FLAGS_EMPTY } = require('../consts');
const users : {[key: string]: User} = {
'toof': {
uin: '156089',
username: 'toof',
password: 'foo',
memberSince: new Date('December 17, 1998 03:24:00'),
}
};
export default class AuthorizationRegistrationService extends BaseService {
private cipher : string;
constructor(communicator : Communicator) {
super({ service: 0x17, version: 0x01 }, [0x02, 0x06], communicator);
this.cipher = "HARDY";
}
override handleMessage(message : FLAP) {
if (message.payload instanceof Buffer) {
console.log('Wont handle Buffer payload');
return;
}
switch (message.payload.subtype) {
case 0x02: // Client login request (md5 login sequence)
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 = payload.find((tlv) => tlv instanceof TLV && tlv.type === TLVType.User);
if (!userTLV || !(userTLV instanceof TLV)) {
return;
}
const username = userTLV.payload.toString('ascii');
if (!users[username]) {
const authResp = new FLAP(2, this.nextReqID,
new SNAC(0x17, 0x03, [
TLV.forUsername(username), // username
TLV.forError(ErrorCode.IncorrectNick) // incorrect nick/password
]));
this.send(authResp);
return;
}
const passwordHashTLV = payload.find((tlv) => tlv instanceof TLV && tlv.type === TLVType.PasswordHash);
if (!passwordHashTLV || !(passwordHashTLV instanceof TLV)) {
return;
}
const pwHash = crypto.createHash('md5');
pwHash.update(this.cipher);
pwHash.update(users[username].password);
pwHash.update(AIM_MD5_STRING);
const digest = pwHash.digest('hex');
if (digest !== (passwordHashTLV as TLV).payload.toString('hex')) {
console.log('Invalid password for', username);
const authResp = new FLAP(2, this.nextReqID,
new SNAC(0x17, 0x03, [
TLV.forUsername(username), // username
TLV.forError(ErrorCode.IncorrectNick) // incorrect nick/password
]));
this.send(authResp);
// Close this connection
const plsLeave = new FLAP(4, this.nextReqID, Buffer.from([]));
this.send(plsLeave);
return;
}
const chatHost = this.communicator.socket.localAddress.split(':').pop() + ':5191';
const authResp = new FLAP(2, this.nextReqID,
new SNAC(0x17, 0x03, [
TLV.forUsername(username), // username
TLV.forBOSAddress(chatHost), // BOS address
TLV.forCookie(JSON.stringify({cookie: 'uwu', user: users[username]})) // Authorization cookie
]));
this.communicator.user = Object.assign({username}, users[username]);
console.log(this.communicator.user);
this.send(authResp);
// tell them to leave
const disconnectResp = new FLAP(4, this.nextReqID, Buffer.alloc(0));
this.send(disconnectResp);
return;
case 0x06: // Request md5 authkey
const MD5AuthKeyHeader = Buffer.alloc(2, 0xFF, 'hex');
MD5AuthKeyHeader.writeUInt16BE(this.cipher.length);
const md5ReqResp = new FLAP(2, this.nextReqID,
new SNAC(0x17, 0x07,
Buffer.concat([MD5AuthKeyHeader, Buffer.from(this.cipher, 'binary')]),
));
this.send(md5ReqResp);
break;
}
}
}
module.exports = AuthorizationRegistrationService;

View file

@ -1,32 +0,0 @@
import Communicator from "../communicator";
import { FLAP } from "../structures";
interface ServiceFamilyVersion {
service : number,
version : number,
}
export default abstract class BaseService {
public service : number;
public version : number;
constructor({service, version} : ServiceFamilyVersion, public supportedSubtypes : number[], public communicator : Communicator) {
this.service = service;
this.version = version;
this.communicator = communicator;
this.supportedSubtypes = supportedSubtypes;
}
send(message : FLAP) {
this.communicator.send(message);
}
get nextReqID() {
return this.communicator.nextReqID;
}
handleMessage(message : FLAP) : void {
throw new Error(''+
`Unhandled message for service ${this.service.toString(16)} supporting version ${this.version.toString(16)}`);
}
}

View file

@ -1,3 +0,0 @@
export const enum ErrorCode {
IncorrectNick = 0x04,
}

View file

@ -1,71 +0,0 @@
import assert from "assert"
import { SNAC } from "./SNAC";
import { logDataStream } from '../util';
export class FLAP {
static fromBuffer(buf : Buffer) {
assert.equal(buf[0], 0x2a, 'Expected 0x2a at start of FLAP header');
assert(buf.length >= 6, 'Expected at least 6 bytes for FLAP header');
const channel = buf.readInt8(1);
const sequenceNumber = buf.slice(2,4).readInt16BE(0);
const payloadLength = buf.slice(4, 6).readInt16BE(0);
if (buf.length < (6 + payloadLength)) {
throw new Error('FLAP payload larger than available buffer data');
}
let payload : Buffer | SNAC = buf.slice(6, 6 + payloadLength);
if (channel === 2 && payloadLength > 0) {
payload = SNAC.fromBuffer(payload, payloadLength);
}
return new FLAP(channel, sequenceNumber, payload)
}
payloadLength: number;
constructor(public channel: number, public sequenceNumber: number, public payload: Buffer | SNAC) {
this.channel = channel;
this.sequenceNumber = sequenceNumber;
this.payload = payload;
if (payload instanceof SNAC) {
this.payloadLength = payload.toBuffer().length;
} else {
this.payloadLength = payload.length;
}
}
/**
* @returns Returns the byte length of this FLAP, includes header and payload
*/
get length() {
return 6 + this.payloadLength;
}
toString() {
let payload = this.payload.toString();
if (this.payload instanceof Buffer) {
payload = logDataStream(this.payload).split('\n').join('\n ');
}
return `ch:${this.channel}, dn: ${this.sequenceNumber}, len: ${this.payloadLength}, payload:\n ${payload}`
}
toBuffer() {
const FLAPHeader = Buffer.alloc(6, 0, 'hex');
FLAPHeader.writeInt8(0x2a, 0);
FLAPHeader.writeInt8(this.channel, 1);
FLAPHeader.writeInt16BE(this.sequenceNumber, 2);
FLAPHeader.writeInt16BE(this.payloadLength, 4);
let payload = this.payload;
if (payload instanceof SNAC) {
payload = payload.toBuffer();
}
return Buffer.concat([FLAPHeader, payload]);
}
}

View file

@ -1,134 +0,0 @@
import assert from "assert";
import { FLAGS_EMPTY } from "../consts";
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 service : number, public subtype : number) {}
toBuffer() : Buffer {
const buf = Buffer.alloc(4, 0x00);
buf.writeInt16BE(this.service, 0);
buf.writeInt16BE(this.subtype, 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()]);
}
}
let snacID = 0x2000;
export class SNAC {
constructor(public service : number, public subtype : number, public payload : (TLV[] | Buffer) = Buffer.alloc(0), public requestID : number = 0, public flags : Buffer = FLAGS_EMPTY) {
this.service = service;
this.subtype = subtype;
this.payload = payload;
this.requestID = requestID || (snacID++);
this.flags = flags;
}
static fromBuffer(buf : Buffer, payloadLength = 0) {
assert(buf.length >= 10, 'Expected 10 bytes for SNAC header');
const service = buf.slice(0,2).readInt16BE(0);
const subtype = buf.slice(2,4).readInt16BE(0);
const flags = buf.slice(4, 6);
const requestID = buf.slice(6, 10).readInt32BE(0);
let payload : Buffer | TLV[]; // SNACs can have multiple payload
// Some SNACs don't have TLV payloads
// Maybe this should be something that the service does itself when it
// wants to respond to a message;
if (service === 0x01 && subtype === 0x17 ||
service === 0x01 && subtype === 0x14 ||
service === 0x01 && subtype === 0x07 ||
service === 0x01 && subtype === 0x08 ||
service === 0x01 && subtype === 0x0e ||
service === 0x04 && subtype === 0x02 ||
service === 0x09 && subtype === 0x04 ||
service === 0x0a && subtype === 0x02 ||
service === 0x04 && subtype === 0x06) {
payload = buf.slice(10, 10 + payloadLength);
} else {
payload = TLV.fromBufferBlob(buf.slice(10));
}
return new SNAC(service, subtype, payload, requestID, flags);
}
static forRateClass(service : number, subtype : 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(service, subtype, payload);
}
toString() {
return `SNAC(${this.service.toString(16)},${this.subtype.toString(16)}) #${this.requestID}\n ${this.payload}`;
}
toBuffer() {
const SNACHeader = Buffer.alloc(10, 0, 'hex');
SNACHeader.writeUInt16BE(this.service);
SNACHeader.writeUInt16BE(this.subtype, 2);
SNACHeader.set(this.flags, 4);
SNACHeader.writeUInt32BE(this.requestID, 6);
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,92 +0,0 @@
import {ErrorCode} from "./ErrorCode";
import { USER_STATUS_VARIOUS, USER_STATUS } from "../consts";
import { char, word, dword } from './bytes';
export const enum TLVType {
User = 0x01,
ClientName = 0x03,
GetServices = 0x06,
PasswordHash = 0x25,
}
export class TLV {
get length() : number {
return this.payload.length;
}
static fromBuffer(buf : Buffer) {
const type = buf.slice(0, 2).readInt16BE(0);
const len = buf.slice(2, 4).readInt16BE(0)
const payload = buf.slice(4, 4 + len);
return new TLV(type, payload);
}
/**
* Extract all TLVs from a given Buffer
* @param buf Buffer that contains multiple TLVs
* @param payloadLength Total stated length of the payload
* @returns all TLVs found in the Buffer
*/
static fromBufferBlob(buf : Buffer) : TLV[] {
const tlvs : TLV[] = [];
// Try to parse TLVs
let payloadIdx = 0;
let cb = 0, cbLimit = 20; //circuit breaker
while (payloadIdx < buf.length && cb < cbLimit) {
const tlv = TLV.fromBuffer(buf.slice(payloadIdx));
tlvs.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);
}
return tlvs;
}
static forUsername(username : string) : TLV {
return new TLV(0x01, Buffer.from(username));
}
static forBOSAddress(address : string ) : TLV {
return new TLV(0x05, Buffer.from(address));
}
static forCookie(cookie : string) : TLV {
return new TLV(0x06, Buffer.from(cookie));
}
static forError(errorCode : ErrorCode) : TLV {
return new TLV(0x08, Buffer.from([0x00, errorCode]));
}
static forStatus(various : USER_STATUS_VARIOUS, status: USER_STATUS) {
const varbuf = Buffer.alloc(2, 0x00);
varbuf.writeUInt16BE(various);
const statbuf = Buffer.alloc(2, 0x00);
statbuf.writeUInt16BE(status);
return new TLV(0x06, Buffer.concat([varbuf, statbuf]));
}
constructor(public type : number, public payload : Buffer) {
this.type = type;
this.payload = payload;
}
toString() {
return `TLV(0x${this.type.toString(16).padStart(2, '0')}, ${this.length}, ${this.payload.toString('ascii')})`;
}
toBuffer() {
return Buffer.concat([
word(this.type),
word(this.length),
this.payload,
]);
}
}

View file

@ -1,43 +0,0 @@
export function char(num : number) : Buffer {
const buf = Buffer.alloc(1, 0x00);
buf.writeUInt8(num);
return buf;
}
export function word(num : number) : Buffer {
const buf = Buffer.alloc(2, 0x00);
buf.writeUInt16BE(num);
return buf;
}
export function dword(num : number) : Buffer {
const buf = Buffer.alloc(4, 0x00);
buf.writeUInt32BE(num);
return buf;
}
export function qword(num : number) : Buffer {
const buf = Buffer.alloc(8, 0x00);
buf.writeUInt32BE(num);
return buf;
}
/**
* Converts a string IP address to it's number representation.
* From: https://stackoverflow.com/a/8105740
* @param ip IP address string
* @returns IP address as a number
*/
export function dot2num(ip : string) : number {
const d = ip.split('.');
return ((((((+d[0])*256)+(+d[1]))*256)+(+d[2]))*256)+(+d[3]);
}
export function num2dot(num : number) : string {
let d = '' + num%256;
for (var i = 3; i > 0; i--) {
num = Math.floor(num/256);
d = num%256 + '.' + d;
}
return d;
}

View file

@ -1,4 +0,0 @@
export * from "./ErrorCode";
export * from "./TLV";
export * from "./SNAC";
export * from "./FLAP";

View file

@ -1,295 +0,0 @@
import { FLAP } from './structures'
import { dot2num, num2dot } from './structures/bytes'
export function chunkString(str : string, len : number) {
const size = Math.ceil(str.length/len)
const r = Array(size)
let offset = 0
for (let i = 0; i < size; i++) {
r[i] = str.substr(offset, len)
offset += len
}
return r
}
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,
isParam? : boolean,
isTLV? : boolean,
dump? : 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};
}
function param(size: DataSize, description: string) : Spec {
return {size, description, isParam: true};
}
function tlv(description : string) : Spec {
return {size : -1, description, isTLV: true};
}
function dump() : Spec {
return {size: -1, description: '', dump: true};
}
function parseBuffer(buf : Buffer, spec : Spec[], repeatTimes = 0) {
let offset = 0;
let rows = [];
let repeat = repeatTimes;
for (let section of spec) {
let value : any = 0;
let bufStr : string = '';
if (section.dump) {
rows.push({raw: logDataStream(buf.slice(offset))});
break;
}
if (section.size === 8) {
bufStr = buf.slice(offset, offset + 1).toString('hex');
value = buf.readInt8(offset);
offset += 1;
} else if (section.size === 16) {
bufStr = buf.slice(offset, offset + 2).toString('hex');
value = buf.readUInt16BE(offset);
offset += 2;
} else if (section.size === 32) {
bufStr = buf.slice(offset, offset + 4).toString('hex');
value = buf.readUInt32BE(offset);
offset += 4;
}
if (section.description.includes("IP")) {
value = num2dot(value);
}
if (section.isParam) {
const paramBuf = buf.slice(offset, offset + value);
offset += value;
rows.push([chunkString(paramBuf.toString('hex'), 2), paramBuf.toString('ascii'), section.description])
} else if (section.isTLV) {
const tlvType = buf.slice(offset, offset + 2).toString('hex');
offset += 2;
const tlvLength = buf.slice(offset, offset + 2).readUInt16BE(0);
offset += 2;
const tlvData = buf.slice(offset, offset + tlvLength);
offset += tlvLength;
let data = tlvData.toString('ascii') as string;
if (section.description.includes("IP")) {
data = num2dot(tlvData.readUInt32BE(0));
}
rows.push([
chunkString(tlvData.toString('hex'), 2),
data,
tlvType + ':' + section.description,
]);
} else 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);
} else {
rows.push([chunkString(bufStr, 2).join(' '), value, section.description]);
}
}
return rows;
}
function printBuffer(buf : Buffer, spec : Spec[]) {
const rows = parseBuffer(buf, spec);
const lastRow = rows[rows.length - 1];
if (!!lastRow.raw) {
console.log((new Table(rows.slice(0, -1))).toString());
console.log(lastRow.raw);
} else {
console.log((new Table(rows)).toString());
}
}
function bufferFromWebText(webtext : string) : Buffer {
return Buffer.from(webtext.replace(/\s/g, ''), 'hex');
}
const SNAC_01_0F = [
byte("FLAP Header"),
byte("Channel"),
word("Sequence ID"),
word("Payload Length"),
word("SNAC Family"),
word("SNAC Subtype"),
word("SNAC Flags"),
dword("SNAC Request-ID"),
param(8, "UIN String Length"),
word("Warning Level"),
word("Number of TLV in list"),
tlv("User Class"),
tlv("User Status"),
tlv("External IP Address"),
tlv("Client Idle Time"),
tlv("Signon Time"),
tlv("Unknown Value"),
tlv("Member Since"),
word("DC Info"),
word("DC Info Length"),
dword("DC Internal IP Address"),
dword("DC TCP Port"),
dword("DC Type"),
word("DC Protocol Version"),
dword("DC Auth Cookie"),
dword("Web Front Port"),
dword("Client Features"),
dword("Last Info Update Time"),
dword("Last EXT info update time"),
dword("Last EXT status update time"),
];
const exSNAC_01_0F = ''+
`
2a 02 00 05 00 71 00 01
00 0f 00 00 00 00 00 00
03 34 30 30 00 00 00 08
00 01 00 01 80 00 06 00
04 00 00 04 01 00 0a 00
04 c0 a8 01 fe 00 0f 00
04 00 00 00 00 00 03 00
04 61 3f aa 53 00 1e 00
04 00 00 00 00 00 05 00
04 36 78 bf a0 00 0c 00
26 c0 a8 01 fe 00 00 16
44 04 00 00 00 04 00 00
00 00 00 00 00 00 00 00
00 03 00 00 00 00 00 00
00 00 00 00 00 00 00
`;
const SNAC_01_07 = [
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'),
]),
dump(),
];
const exSNAC_01_07 = ''+
`
2a 02 00 05 03 3b 00 01 00 07 00 00 00 00
00 00 00 05 00 01 00 00 00 50 00 00 09 c4 00 00
07 d0 00 00 05 dc 00 00 03 20 00 00 16 dc 00 00
17 70 00 00 00 00 00 00 02 00 00 00 50 00 00 0b
b8 00 00 07 d0 00 00 05 dc 00 00 03 e8 00 00 17
70 00 00 17 70 00 00 00 7b 00 00 03 00 00 00 1e
00 00 0e 74 00 00 0f a0 00 00 05 dc 00 00 03 e8
00 00 17 70 00 00 17 70 00 00 00 00 00 00 04 00
00 00 14 00 00 15 7c 00 00 14 b4 00 00 10 68 00
00 0b b8 00 00 17 70 00 00 1f 40 00 00 00 7b 00
00 05 00 00 00 0a 00 00 15 7c 00 00 14 b4 00 00
10 68 00 00 0b b8 00 00 17 70 00 00 1f 40 00 00
00 7b 00 00 01 00 91 00 01 00 01 00 01 00 02 00
01 00 03 00 01 00 04 00 01 00 05 00 01 00 06 00
01 00 07 00 01 00 08 00 01 00 09 00 01 00 0a 00
01 00 0b 00 01 00 0c 00 01 00 0d 00 01 00 0e 00
01 00 0f 00 01 00 10 00 01 00 11 00 01 00 12 00
01 00 13 00 01 00 14 00 01 00 15 00 01 00 16 00
01 00 17 00 01 00 18 00 01 00 19 00 01 00 1a 00
01 00 1b 00 01 00 1c 00 01 00 1d 00 01 00 1e 00
01 00 1f 00 01 00 20 00 01 00 21 00 02 00 01 00
02 00 02 00 02 00 03 00 02 00 04 00 02 00 06 00
02 00 07 00 02 00 08 00 02 00 0a 00 02 00 0c 00
02 00 0d 00 02 00 0e 00 02 00 0f 00 02 00 10 00
02 00 11 00 02 00 12 00 02 00 13 00 02 00 14 00
02 00 15 00 03 00 01 00 03 00 02 00 03 00 03 00
03 00 06 00 03 00 07 00 03 00 08 00 03 00 09 00
03 00 0a 00 03 00 0b 00 03 00 0c 00 04 00 01 00
04 00 02 00 04 00 03 00 04 00 04 00 04 00 05 00
04 00 07 00 04 00 08 00 04 00 09 00 04 00 0a 00
04 00 0b 00 04 00 0c 00 04 00 0d 00 04 00 0e 00
04 00 0f 00 04 00 10 00 04 00 11 00 04 00 12 00
04 00 13 00 04 00 14 00 06 00 01 00 06 00 02 00
06 00 03 00 08 00 01 00 08 00 02 00 09 00 01 00
09 00 02 00 09 00 03 00 09 00 04 00 09 00 09 00
09 00 0a 00 09 00 0b 00 0a 00 01 00 0a 00 02 00
0a 00 03 00 0b 00 01 00 0b 00 02 00 0b 00 03 00
0b 00 04 00 0c 00 01 00 0c 00 02 00 0c 00 03 00
13 00 01 00 13 00 02 00 13 00 03 00 13 00 04 00
13 00 05 00 13 00 06 00 13 00 07 00 13 00 08 00
13 00 09 00 13 00 0a 00 13 00 0b 00 13 00 0c 00
13 00 0d 00 13 00 0e 00 13 00 0f 00 13 00 10 00
13 00 11 00 13 00 12 00 13 00 13 00 13 00 14 00
13 00 15 00 13 00 16 00 13 00 17 00 13 00 18 00
13 00 19 00 13 00 1a 00 13 00 1b 00 13 00 1c 00
13 00 1d 00 13 00 1e 00 13 00 1f 00 13 00 20 00
13 00 21 00 13 00 22 00 13 00 23 00 13 00 24 00
13 00 25 00 13 00 26 00 13 00 27 00 13 00 28 00
15 00 01 00 15 00 02 00 15 00 03 00 02 00 06 00
03 00 04 00 03 00 05 00 09 00 05 00 09 00 06 00
09 00 07 00 09 00 08 00 03 00 02 00 02 00 05 00
04 00 06 00 04 00 02 00 02 00 09 00 02 00 0b 00
05 00 00
`;
if (require.main === module) {
printBuffer(bufferFromWebText(exSNAC_01_07), SNAC_01_07);
}

View file

@ -1,47 +0,0 @@
import assert from 'assert';
import { FLAP, SNAC, TLV } from '../src/structures';
import {FLAGS_EMPTY} from "../src/consts";
const tests = [
() => {
// Construct and test a CLI_AUTH_REQUEST
const md5_auth_req = new FLAP(0x02, 0, new SNAC(0x17, 0x06, [new TLV(0x01, Buffer.from("toof"))]));
assert(md5_auth_req.channel === 2);
assert(md5_auth_req.payload instanceof SNAC);
assert(md5_auth_req.payload.service === 23);
assert(md5_auth_req.payload.subtype === 6);
assert(md5_auth_req.payload.payload.length === 1);
assert.equal(md5_auth_req.payload.payload.length, 1);
assert(md5_auth_req.payload.payload[0] instanceof TLV);
},
() => {
// Test FLAP.length calculation and consuming multiple messages
const dataStr = `
2a 02 4b 11 00 0a 00 01
00 0e 00 00 00 00 00 00
2a 02 4b 12 00 0a 00 02
00 02 00 00 00 00 00 00
`.trim().replace(/\s+/g, '');
let data = Buffer.from(dataStr, 'hex');
const message = FLAP.fromBuffer(data);
assert(message.channel === 2);
assert(message.payloadLength === 10);
assert(message.length === 16);
assert(message.payload instanceof SNAC);
assert((message.payload as SNAC).service === 1);
assert((message.payload as SNAC).subtype === 0x0e);
data = data.slice(message.length);
const secondMessage = FLAP.fromBuffer(data);
assert(secondMessage.length === 16);
assert(secondMessage.payload instanceof SNAC);
assert((secondMessage.payload as SNAC).service === 2);
assert((secondMessage.payload as SNAC).subtype === 0x02);
}
];
tests.forEach((testFn) => {
testFn();
});

View file

@ -1,10 +0,0 @@
{
"extends": "@tsconfig/node14/tsconfig.json",
"compilerOptions": {
"preserveConstEnums": true,
"esModuleInterop": true,
"outDir": "./dist",
},
"include": ["src/**/*", "tests/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}

173
yarn.lock
View file

@ -2,14 +2,6 @@
# yarn lockfile v1 # 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": "@sindresorhus/is@^0.14.0":
version "0.14.0" version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
@ -22,16 +14,6 @@
dependencies: dependencies:
defer-to-connect "^1.0.1" defer-to-connect "^1.0.1"
"@tsconfig/node14@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2"
integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==
"@types/node@^16.7.13":
version "16.7.13"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.13.tgz#86fae356b03b5a12f2506c6cf6cd9287b205973f"
integrity sha512-pLUPDn+YG3FYEt/pHI74HmnJOWzeR+tOIQzUx93pi9M7D8OE7PSLr97HboXwk5F+JS+TLtWuzCOW97AHjmOXXA==
abbrev@1: abbrev@1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
@ -54,13 +36,6 @@ ansi-regex@^5.0.0:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== 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: ansi-styles@^4.1.0:
version "4.3.0" version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
@ -76,21 +51,6 @@ anymatch@~3.1.2:
normalize-path "^3.0.0" normalize-path "^3.0.0"
picomatch "^2.0.4" 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: balanced-match@^1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@ -148,15 +108,6 @@ camelcase@^5.3.1:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== 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: chalk@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
@ -197,13 +148,6 @@ clone-response@^1.0.2:
dependencies: dependencies:
mimic-response "^1.0.0" 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: color-convert@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
@ -211,36 +155,11 @@ color-convert@^2.0.1:
dependencies: dependencies:
color-name "~1.1.4" 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: color-name@~1.1.4:
version "1.1.4" version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 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: concat-map@0.0.1:
version "0.0.1" version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@ -284,7 +203,7 @@ decompress-response@^3.3.0:
dependencies: dependencies:
mimic-response "^1.0.0" mimic-response "^1.0.0"
deep-extend@^0.6.0, deep-extend@~0.6.0: deep-extend@^0.6.0:
version "0.6.0" version "0.6.0"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
@ -328,11 +247,6 @@ escape-goat@^2.0.0:
resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== 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: fill-range@^7.0.1:
version "7.0.1" version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
@ -340,13 +254,6 @@ fill-range@^7.0.1:
dependencies: dependencies:
to-regex-range "^5.0.1" 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: fsevents@~2.3.2:
version "2.3.2" version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
@ -540,16 +447,6 @@ latest-version@^5.0.0:
dependencies: dependencies:
package-json "^6.3.0" 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: lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
@ -696,11 +593,6 @@ readdirp@~3.6.0:
dependencies: dependencies:
picomatch "^2.2.1" 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: registry-auth-token@^4.0.0:
version "4.2.1" version "4.2.1"
resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250"
@ -744,11 +636,6 @@ signal-exit@^3.0.2:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== 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: string-width@^3.0.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
@ -786,7 +673,7 @@ strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
supports-color@^5.3.0, supports-color@^5.5.0: supports-color@^5.5.0:
version "5.5.0" version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
@ -800,29 +687,6 @@ supports-color@^7.1.0:
dependencies: dependencies:
has-flag "^4.0.0" 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: term-size@^2.1.0:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54"
@ -859,26 +723,6 @@ typedarray-to-buffer@^3.1.5:
dependencies: dependencies:
is-typedarray "^1.0.0" is-typedarray "^1.0.0"
typescript@^4.4.2:
version "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: undefsafe@^2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae"
@ -926,19 +770,6 @@ widest-line@^3.1.0:
dependencies: dependencies:
string-width "^4.0.0" 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: wrappy@1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"