mirror of
https://github.com/amigan/aim-oscar-server.git
synced 2024-11-22 04:29:47 -05:00
Convert everything to TypeScript
This commit is contained in:
parent
4c2492e348
commit
9d628233e1
12 changed files with 114 additions and 80 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
node_modules
|
node_modules
|
||||||
built
|
built
|
||||||
|
dist
|
||||||
|
|
|
@ -4,11 +4,12 @@
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon ./src/index.js",
|
"dev": "nodemon --ignore ./dist/ --watch ./src -e ts --exec 'tsc && node ./dist/index.js'",
|
||||||
"start": "node ./src/index.js"
|
"start": "tsc && node ./dist/index.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/node14": "^1.0.1",
|
"@tsconfig/node14": "^1.0.1",
|
||||||
|
"@types/node": "^16.7.13",
|
||||||
"nodemon": "^2.0.12",
|
"nodemon": "^2.0.12",
|
||||||
"typescript": "^4.4.2"
|
"typescript": "^4.4.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,29 @@
|
||||||
const { FLAP, SNAC, TLV } = require('./structures');
|
import net from "net";
|
||||||
const { logDataStream } = require('./util');
|
import { FLAP, SNAC, TLV } from './structures';
|
||||||
const { FLAGS_EMPTY } = require('./consts');
|
import { logDataStream } from './util';
|
||||||
|
import { FLAGS_EMPTY } from './consts';
|
||||||
|
|
||||||
const AuthorizationRegistrationService = require("./services/authorization-registration");
|
import AuthorizationRegistrationService from "./services/authorization-registration";
|
||||||
|
import BaseService from "./services/base";
|
||||||
|
|
||||||
class Communicator {
|
export default class Communicator {
|
||||||
constructor(socket) {
|
|
||||||
|
private _sequenceNumber = 0;
|
||||||
|
private services : {[key: number]: BaseService} = {};
|
||||||
|
|
||||||
|
constructor(public socket : net.Socket) {
|
||||||
// Hold on to the socket
|
// Hold on to the socket
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
|
|
||||||
this.socket.on('data', (data) => {
|
this.socket.on('data', (data : Buffer) => {
|
||||||
console.log('DATA-----------------------');
|
console.log('DATA-----------------------');
|
||||||
const flap = FLAP.fromBuffer(Buffer.from(data, 'hex'));
|
const flap = FLAP.fromBuffer(data);
|
||||||
console.log('RECV', flap.toString());
|
console.log('RECV', flap.toString());
|
||||||
console.log('RAW\n' + logDataStream(Buffer.from(data, 'hex')));
|
console.log('RAW\n' + logDataStream(data));
|
||||||
this.handleMessage(flap);
|
this.handleMessage(flap);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._sequenceNumber = 0;
|
|
||||||
|
|
||||||
this.registerServices();
|
this.registerServices();
|
||||||
|
|
||||||
this.start();
|
this.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,16 +48,21 @@ class Communicator {
|
||||||
return ++this._sequenceNumber;
|
return ++this._sequenceNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
send(message) {
|
send(message : FLAP) {
|
||||||
console.log('SEND', message.toString());
|
console.log('SEND', message.toString());
|
||||||
console.log('RAW\n' + logDataStream(message.toBuffer()));
|
console.log('RAW\n' + logDataStream(message.toBuffer()));
|
||||||
console.log('-----------------------DATA');
|
console.log('-----------------------DATA');
|
||||||
this.socket.write(message.toBuffer());
|
this.socket.write(message.toBuffer());
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMessage(message) {
|
handleMessage(message : FLAP) {
|
||||||
switch (message.channel) {
|
switch (message.channel) {
|
||||||
case 1:
|
case 1:
|
||||||
|
// No SNACs on channel 1
|
||||||
|
if (!(message.payload instanceof Buffer)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const protocol = message.payload.readUInt32BE();
|
const protocol = message.payload.readUInt32BE();
|
||||||
|
|
||||||
if (protocol !== 1) {
|
if (protocol !== 1) {
|
||||||
|
@ -73,7 +81,7 @@ class Communicator {
|
||||||
switch (tlv.type) {
|
switch (tlv.type) {
|
||||||
case 0x06: // Requesting available services
|
case 0x06: // Requesting available services
|
||||||
// this is just a dword list of service families
|
// this is just a dword list of service families
|
||||||
const servicesOffered = [];
|
const servicesOffered : Buffer[] = [];
|
||||||
Object.values(this.services).forEach((service) => {
|
Object.values(this.services).forEach((service) => {
|
||||||
servicesOffered.push(Buffer.from([0x00, service.family]));
|
servicesOffered.push(Buffer.from([0x00, service.family]));
|
||||||
});
|
});
|
||||||
|
@ -87,8 +95,8 @@ class Communicator {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
case 2:
|
case 2:
|
||||||
if (!message.payload) {
|
if (!(message.payload instanceof SNAC)) {
|
||||||
console.error('No SNAC');
|
console.error('Expected SNAC payload');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,5 +113,3 @@ class Communicator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Communicator;
|
|
||||||
|
|
|
@ -1,7 +1,2 @@
|
||||||
const AIM_MD5_STRING = "AOL Instant Messenger (SM)";
|
export const AIM_MD5_STRING = "AOL Instant Messenger (SM)";
|
||||||
const FLAGS_EMPTY = Buffer.from([0x00, 0x00, 0x00, 0x00]);
|
export const FLAGS_EMPTY = Buffer.from([0x00, 0x00, 0x00, 0x00]);
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
AIM_MD5_STRING,
|
|
||||||
FLAGS_EMPTY,
|
|
||||||
};
|
|
||||||
|
|
12
src/index.ts
12
src/index.ts
|
@ -1,7 +1,5 @@
|
||||||
const net = require('net');
|
import net from 'net';
|
||||||
const Communicator = require('./communicator');
|
import Communicator from './communicator';
|
||||||
|
|
||||||
const communicators = {};
|
|
||||||
|
|
||||||
const server = net.createServer((socket) => {
|
const server = net.createServer((socket) => {
|
||||||
console.log('client connected...');
|
console.log('client connected...');
|
||||||
|
@ -9,21 +7,19 @@ const server = net.createServer((socket) => {
|
||||||
|
|
||||||
socket.on('error', (e) => {
|
socket.on('error', (e) => {
|
||||||
console.error('socket encountered an error:', e);
|
console.error('socket encountered an error:', e);
|
||||||
delete communicators[socket];
|
socket.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('timeout', () => {
|
socket.on('timeout', () => {
|
||||||
console.log('socket timeout');
|
console.log('socket timeout');
|
||||||
socket.end();
|
socket.end();
|
||||||
delete communicators[socket];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('end', () => {
|
socket.on('end', () => {
|
||||||
console.log('client disconnected...');
|
console.log('client disconnected...');
|
||||||
});
|
});
|
||||||
|
|
||||||
const c = new Communicator(socket);
|
new Communicator(socket);
|
||||||
communicators[socket] = c;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
server.on('error', (err) => {
|
server.on('error', (err) => {
|
||||||
|
|
|
@ -1,27 +1,42 @@
|
||||||
const crypto = require('crypto');
|
import crypto from 'crypto';
|
||||||
const BaseService = require('./base');
|
import BaseService from './base';
|
||||||
const { FLAP, SNAC, TLV } = require('../structures');
|
import Communicator from '../communicator';
|
||||||
|
import { FLAP, SNAC, TLV } from '../structures';
|
||||||
|
|
||||||
const { AIM_MD5_STRING, FLAGS_EMPTY } = require('../consts');
|
const { AIM_MD5_STRING, FLAGS_EMPTY } = require('../consts');
|
||||||
|
|
||||||
const users = {
|
const users : {[key: string]: string} = {
|
||||||
toof: 'foo',
|
'toof': 'foo',
|
||||||
};
|
};
|
||||||
|
|
||||||
class AuthorizationRegistrationService extends BaseService {
|
export default class AuthorizationRegistrationService extends BaseService {
|
||||||
constructor(communicator) {
|
private cipher : string;
|
||||||
|
|
||||||
|
constructor(communicator : Communicator) {
|
||||||
super({ family: 0x17, version: 0x01 }, communicator);
|
super({ family: 0x17, version: 0x01 }, communicator);
|
||||||
this.cipher = "HARDY";
|
this.cipher = "HARDY";
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMessage(message) {
|
override handleMessage(message : FLAP) {
|
||||||
|
if (message.payload instanceof Buffer) {
|
||||||
|
console.log('Wont handle Buffer payload');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (message.payload.service) {
|
switch (message.payload.service) {
|
||||||
case 0x02: // Client login request (md5 login sequence)
|
case 0x02: // Client login request (md5 login sequence)
|
||||||
const tlvs = message.payload.tlvs;
|
const tlvs = message.payload.tlvs;
|
||||||
const clientNameTLV = tlvs.find((tlv) => tlv.type === 0x03);
|
const clientNameTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === 0x03);
|
||||||
|
if (!clientNameTLV || !(clientNameTLV instanceof TLV)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.log("Attempting connection from", clientNameTLV.payload.toString('ascii'));
|
console.log("Attempting connection from", clientNameTLV.payload.toString('ascii'));
|
||||||
|
|
||||||
const userTLV = tlvs.find((tlv) => tlv.type === 0x01);
|
const userTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === 0x01);
|
||||||
|
if (!userTLV || !(userTLV instanceof TLV)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const username = userTLV.payload.toString('ascii');
|
const username = userTLV.payload.toString('ascii');
|
||||||
|
|
||||||
if (!users[username]) {
|
if (!users[username]) {
|
||||||
|
@ -35,7 +50,10 @@ class AuthorizationRegistrationService extends BaseService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const passwordHashTLV = tlvs.find((tlv) => tlv.type === 0x25);
|
const passwordHashTLV = tlvs.find((tlv) => tlv instanceof TLV && tlv.type === 0x25);
|
||||||
|
if (!passwordHashTLV || !(passwordHashTLV instanceof TLV)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const pwHash = crypto.createHash('md5');
|
const pwHash = crypto.createHash('md5');
|
||||||
pwHash.update(this.cipher);
|
pwHash.update(this.cipher);
|
||||||
|
@ -43,7 +61,7 @@ class AuthorizationRegistrationService extends BaseService {
|
||||||
pwHash.update(AIM_MD5_STRING);
|
pwHash.update(AIM_MD5_STRING);
|
||||||
const digest = pwHash.digest('hex');
|
const digest = pwHash.digest('hex');
|
||||||
|
|
||||||
if (digest !== passwordHashTLV.payload.toString('hex')) {
|
if (digest !== (passwordHashTLV as TLV).payload.toString('hex')) {
|
||||||
console.log('Invalid password for', username);
|
console.log('Invalid password for', username);
|
||||||
const authResp = new FLAP(2, this._getNewSequenceNumber(),
|
const authResp = new FLAP(2, this._getNewSequenceNumber(),
|
||||||
new SNAC(0x17, 0x03, FLAGS_EMPTY, 0, [
|
new SNAC(0x17, 0x03, FLAGS_EMPTY, 0, [
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
class BaseService {
|
import Communicator from "../communicator";
|
||||||
constructor({family, version}, communicator) {
|
import { FLAP } from "../structures";
|
||||||
|
|
||||||
|
interface ServiceFamilyVersion {
|
||||||
|
family : number,
|
||||||
|
version : number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class BaseService {
|
||||||
|
public family : number;
|
||||||
|
public version : number;
|
||||||
|
|
||||||
|
constructor({family, version} : ServiceFamilyVersion, public communicator : Communicator) {
|
||||||
this.family = family;
|
this.family = family;
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.communicator = communicator;
|
this.communicator = communicator;
|
||||||
}
|
}
|
||||||
|
|
||||||
send(message) {
|
send(message : FLAP) {
|
||||||
this.communicator.send(message);
|
this.communicator.send(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,9 +24,7 @@ class BaseService {
|
||||||
return this.communicator._getNewSequenceNumber();
|
return this.communicator._getNewSequenceNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMessage(message) {
|
handleMessage(message : FLAP) : void {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = BaseService;
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
const assert = require('assert');
|
import assert from 'assert';
|
||||||
|
|
||||||
const { logDataStream } = require('./util');
|
import { logDataStream } from './util';
|
||||||
|
|
||||||
class TLV {
|
export class TLV {
|
||||||
static fromBuffer(buf) {
|
static fromBuffer(buf : Buffer) {
|
||||||
const type = buf.slice(0, 2).readInt16BE(0);
|
const type = buf.slice(0, 2).readInt16BE(0);
|
||||||
const len = buf.slice(2, 4).readInt16BE(0)
|
const len = buf.slice(2, 4).readInt16BE(0)
|
||||||
const payload = buf.slice(4, 4 + len);
|
const payload = buf.slice(4, 4 + len);
|
||||||
|
@ -11,7 +11,9 @@ class TLV {
|
||||||
return new TLV(type, payload);
|
return new TLV(type, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(type, payload) {
|
public len : number;
|
||||||
|
|
||||||
|
constructor(public type : number, public payload : Buffer) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.len = payload.length;
|
this.len = payload.length;
|
||||||
this.payload = payload;
|
this.payload = payload;
|
||||||
|
@ -29,14 +31,14 @@ class TLV {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SNAC {
|
export class SNAC {
|
||||||
static fromBuffer(buf, payloadLength = 0) {
|
static fromBuffer(buf : Buffer, payloadLength = 0) {
|
||||||
assert(buf.length >= 10, 'Expected 10 bytes for SNAC header');
|
assert(buf.length >= 10, 'Expected 10 bytes for SNAC header');
|
||||||
const family = buf.slice(0,2).readInt16BE(0);
|
const family = buf.slice(0,2).readInt16BE(0);
|
||||||
const service = buf.slice(2,4).readInt16BE(0);
|
const service = buf.slice(2,4).readInt16BE(0);
|
||||||
const flags = buf.slice(4, 6);
|
const flags = buf.slice(4, 6);
|
||||||
const requestID = buf.slice(6, 10).readInt32BE(0);
|
const requestID = buf.slice(6, 10).readInt32BE(0);
|
||||||
const tlvs = []; // SNACs can have multiple TLVs
|
const tlvs : TLV[] = []; // SNACs can have multiple TLVs
|
||||||
|
|
||||||
let tlvsIdx = 10;
|
let tlvsIdx = 10;
|
||||||
let cb = 0, cbLimit = 20; //circuit breaker
|
let cb = 0, cbLimit = 20; //circuit breaker
|
||||||
|
@ -54,7 +56,7 @@ class SNAC {
|
||||||
return new SNAC(family, service, flags, requestID, tlvs);
|
return new SNAC(family, service, flags, requestID, tlvs);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(family, service, flags, requestID, tlvs = []) {
|
constructor(public family : number, public service : number, public flags : Buffer, public requestID : number , public tlvs : Array<TLV | Buffer> = []) {
|
||||||
this.family = family;
|
this.family = family;
|
||||||
this.service = service;
|
this.service = service;
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
|
@ -70,7 +72,7 @@ class SNAC {
|
||||||
const SNACHeader = Buffer.alloc(10, 0, 'hex');
|
const SNACHeader = Buffer.alloc(10, 0, 'hex');
|
||||||
SNACHeader.writeUInt16BE(this.family);
|
SNACHeader.writeUInt16BE(this.family);
|
||||||
SNACHeader.writeUInt16BE(this.service, 2);
|
SNACHeader.writeUInt16BE(this.service, 2);
|
||||||
SNACHeader.writeUInt16BE(this.flags, 4);
|
SNACHeader.set(this.flags, 4);
|
||||||
SNACHeader.writeUInt32BE(this.requestID, 6);
|
SNACHeader.writeUInt32BE(this.requestID, 6);
|
||||||
|
|
||||||
const payload = this.tlvs.map((thing) => {
|
const payload = this.tlvs.map((thing) => {
|
||||||
|
@ -84,14 +86,14 @@ class SNAC {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FLAP {
|
export class FLAP {
|
||||||
static fromBuffer(buf) {
|
static fromBuffer(buf : Buffer) {
|
||||||
assert.equal(buf[0], 0x2a, 'Expected 0x2a at start of FLAP header');
|
assert.equal(buf[0], 0x2a, 'Expected 0x2a at start of FLAP header');
|
||||||
assert(buf.length >= 6, 'Expected at least 6 bytes for FLAP header');
|
assert(buf.length >= 6, 'Expected at least 6 bytes for FLAP header');
|
||||||
const channel = buf.readInt8(1);
|
const channel = buf.readInt8(1);
|
||||||
const sequenceNumber = buf.slice(2,4).readInt16BE(0);
|
const sequenceNumber = buf.slice(2,4).readInt16BE(0);
|
||||||
const payloadLength = buf.slice(4, 6).readInt16BE(0);
|
const payloadLength = buf.slice(4, 6).readInt16BE(0);
|
||||||
let payload = buf.slice(6, 6 + payloadLength);
|
let payload : Buffer | SNAC = buf.slice(6, 6 + payloadLength);
|
||||||
|
|
||||||
if (channel === 2) {
|
if (channel === 2) {
|
||||||
payload = SNAC.fromBuffer(payload, payloadLength);
|
payload = SNAC.fromBuffer(payload, payloadLength);
|
||||||
|
@ -100,21 +102,26 @@ class FLAP {
|
||||||
return new FLAP(channel, sequenceNumber, payload)
|
return new FLAP(channel, sequenceNumber, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(channel, sequenceNumber, payload) {
|
payloadLength: number;
|
||||||
|
|
||||||
|
constructor(public channel: number, public sequenceNumber: number, public payload: Buffer | SNAC) {
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
this.sequenceNumber = sequenceNumber;
|
this.sequenceNumber = sequenceNumber;
|
||||||
|
|
||||||
this.payload = payload;
|
this.payload = payload;
|
||||||
this.payloadLength = this.payload.length;
|
|
||||||
|
|
||||||
if (payload instanceof SNAC) {
|
if (payload instanceof SNAC) {
|
||||||
this.payloadLength = payload.toBuffer().length;
|
this.payloadLength = payload.toBuffer().length;
|
||||||
|
} else {
|
||||||
|
this.payloadLength = payload.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
const hasSnac = this.payload instanceof SNAC;
|
let payload = this.payload.toString();
|
||||||
const payload = hasSnac ? this.payload.toString() : logDataStream(this.payload).split('\n').join('\n ');
|
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}`
|
return `ch:${this.channel}, dn: ${this.sequenceNumber}, len: ${this.payloadLength}, payload:\n ${payload}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
function chunkString(str, len) {
|
function chunkString(str : string, len : number) {
|
||||||
const size = Math.ceil(str.length/len)
|
const size = Math.ceil(str.length/len)
|
||||||
const r = Array(size)
|
const r = Array(size)
|
||||||
let offset = 0
|
let offset = 0
|
||||||
|
@ -11,11 +11,7 @@ function chunkString(str, len) {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
function logDataStream(data){
|
export function logDataStream(data : Buffer){
|
||||||
const strs = chunkString(data.toString('hex'), 16);
|
const strs = chunkString(data.toString('hex'), 16);
|
||||||
return strs.map((str) => chunkString(str, 2).join(' ')).join('\n');
|
return strs.map((str) => chunkString(str, 2).join(' ')).join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
logDataStream,
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const assert = require('assert');
|
import assert from 'assert';
|
||||||
|
|
||||||
const { FLAP, SNAC, TLV } = require('../src/structures');
|
const { FLAP, SNAC, TLV } = require('../src/structures');
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"extends": "@tsconfig/node14/tsconfig.json",
|
"extends": "@tsconfig/node14/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"preserveConstEnums": true,
|
"preserveConstEnums": true,
|
||||||
"outDir": "./built",
|
"outDir": "./dist",
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules", "**/*.spec.ts"]
|
"exclude": ["node_modules", "**/*.spec.ts"]
|
||||||
|
|
|
@ -19,6 +19,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2"
|
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2"
|
||||||
integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==
|
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"
|
||||||
|
|
Loading…
Reference in a new issue