add geoip country flag support
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,3 +12,6 @@ cvmts/attic
|
|||||||
# Guac-rs
|
# Guac-rs
|
||||||
cvm-rs/target
|
cvm-rs/target
|
||||||
cvm-rs/index.node
|
cvm-rs/index.node
|
||||||
|
|
||||||
|
# geolite shit
|
||||||
|
**/geoip/
|
||||||
@@ -11,6 +11,15 @@ origin = false
|
|||||||
# Origins to accept connections from.
|
# Origins to accept connections from.
|
||||||
originAllowedDomains = ["computernewb.com"]
|
originAllowedDomains = ["computernewb.com"]
|
||||||
|
|
||||||
|
[geoip]
|
||||||
|
# Enables support for showing country flags next to usernames.
|
||||||
|
enabled = false
|
||||||
|
# Directory to store and load GeoIP databases from.
|
||||||
|
directory = "geoip/"
|
||||||
|
# MaxMind license key and account ID (https://www.maxmind.com/en/accounts/current/license-key)
|
||||||
|
accountID = ""
|
||||||
|
licenseKey = ""
|
||||||
|
|
||||||
[tcp]
|
[tcp]
|
||||||
enabled = false
|
enabled = false
|
||||||
host = "0.0.0.0"
|
host = "0.0.0.0"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cvmts/cvm-rs": "*",
|
"@cvmts/cvm-rs": "*",
|
||||||
"@cvmts/qemu": "*",
|
"@cvmts/qemu": "*",
|
||||||
|
"@maxmind/geoip2-node": "^5.0.0",
|
||||||
"execa": "^8.0.1",
|
"execa": "^8.0.1",
|
||||||
"mnemonist": "^0.39.5",
|
"mnemonist": "^0.39.5",
|
||||||
"sharp": "^0.33.3",
|
"sharp": "^0.33.3",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import AuthManager from './AuthManager.js';
|
|||||||
import { Size, Rect, Logger } from '@cvmts/shared';
|
import { Size, Rect, Logger } from '@cvmts/shared';
|
||||||
import { JPEGEncoder } from './JPEGEncoder.js';
|
import { JPEGEncoder } from './JPEGEncoder.js';
|
||||||
import VM from './VM.js';
|
import VM from './VM.js';
|
||||||
|
import { ReaderModel } from '@maxmind/geoip2-node';
|
||||||
|
|
||||||
// Instead of strange hacks we can just use nodejs provided
|
// Instead of strange hacks we can just use nodejs provided
|
||||||
// import.meta properties, which have existed since LTS if not before
|
// import.meta properties, which have existed since LTS if not before
|
||||||
@@ -81,9 +82,12 @@ export default class CollabVMServer {
|
|||||||
// Authentication manager
|
// Authentication manager
|
||||||
private auth: AuthManager | null;
|
private auth: AuthManager | null;
|
||||||
|
|
||||||
|
// Geoip
|
||||||
|
private geoipReader: ReaderModel | null;
|
||||||
|
|
||||||
private logger = new Logger('CVMTS.Server');
|
private logger = new Logger('CVMTS.Server');
|
||||||
|
|
||||||
constructor(config: IConfig, vm: VM, auth: AuthManager | null) {
|
constructor(config: IConfig, vm: VM, auth: AuthManager | null, geoipReader: ReaderModel | null) {
|
||||||
this.Config = config;
|
this.Config = config;
|
||||||
this.ChatHistory = new CircularBuffer<ChatHistory>(Array, this.Config.collabvm.maxChatHistoryLength);
|
this.ChatHistory = new CircularBuffer<ChatHistory>(Array, this.Config.collabvm.maxChatHistoryLength);
|
||||||
this.TurnQueue = new Queue<User>();
|
this.TurnQueue = new Queue<User>();
|
||||||
@@ -127,6 +131,8 @@ export default class CollabVMServer {
|
|||||||
|
|
||||||
// authentication manager
|
// authentication manager
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
|
|
||||||
|
this.geoipReader = geoipReader;
|
||||||
}
|
}
|
||||||
|
|
||||||
public addUser(user: User) {
|
public addUser(user: User) {
|
||||||
@@ -137,12 +143,20 @@ export default class CollabVMServer {
|
|||||||
sameip[0].kick();
|
sameip[0].kick();
|
||||||
}
|
}
|
||||||
this.clients.push(user);
|
this.clients.push(user);
|
||||||
|
if (this.Config.geoip.enabled) {
|
||||||
|
try {
|
||||||
|
user.countryCode = this.geoipReader!.country(user.IP.address).country!.isoCode;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.Warning(`Failed to get country code for ${user.IP.address}: ${(error as Error).message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
user.socket.on('msg', (msg: string) => this.onMessage(user, msg));
|
user.socket.on('msg', (msg: string) => this.onMessage(user, msg));
|
||||||
user.socket.on('disconnect', () => this.connectionClosed(user));
|
user.socket.on('disconnect', () => this.connectionClosed(user));
|
||||||
if (this.Config.auth.enabled) {
|
if (this.Config.auth.enabled) {
|
||||||
user.sendMsg(cvm.guacEncode('auth', this.Config.auth.apiEndpoint));
|
user.sendMsg(cvm.guacEncode('auth', this.Config.auth.apiEndpoint));
|
||||||
}
|
}
|
||||||
user.sendMsg(this.getAdduserMsg());
|
user.sendMsg(this.getAdduserMsg());
|
||||||
|
if (this.Config.geoip.enabled) user.sendMsg(this.getFlagMsg());
|
||||||
}
|
}
|
||||||
|
|
||||||
private connectionClosed(user: User) {
|
private connectionClosed(user: User) {
|
||||||
@@ -196,7 +210,14 @@ export default class CollabVMServer {
|
|||||||
await old.kick();
|
await old.kick();
|
||||||
}
|
}
|
||||||
// Set username
|
// Set username
|
||||||
this.renameUser(client, res.username);
|
if (client.countryCode !== null && client.noFlag) {
|
||||||
|
// privacy
|
||||||
|
for (let cl of this.clients.filter(c => c !== client)) {
|
||||||
|
cl.sendMsg(cvm.guacEncode('remuser', '1', client.username!));
|
||||||
|
}
|
||||||
|
this.renameUser(client, res.username, false);
|
||||||
|
}
|
||||||
|
else this.renameUser(client, res.username, true);
|
||||||
// Set rank
|
// Set rank
|
||||||
client.rank = res.rank;
|
client.rank = res.rank;
|
||||||
if (client.rank === Rank.Admin) {
|
if (client.rank === Rank.Admin) {
|
||||||
@@ -217,6 +238,11 @@ export default class CollabVMServer {
|
|||||||
client.sendMsg(cvm.guacEncode('login', '0', 'There was an internal error while authenticating. Please let a staff member know as soon as possible'));
|
client.sendMsg(cvm.guacEncode('login', '0', 'There was an internal error while authenticating. Please let a staff member know as soon as possible'));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'noflag': {
|
||||||
|
if (client.connectedToNode) // too late
|
||||||
|
return;
|
||||||
|
client.noFlag = true;
|
||||||
|
}
|
||||||
case 'list':
|
case 'list':
|
||||||
client.sendMsg(cvm.guacEncode('list', this.Config.collabvm.node, this.Config.collabvm.displayname, this.screenHidden ? this.screenHiddenThumb : await this.getThumbnail()));
|
client.sendMsg(cvm.guacEncode('list', this.Config.collabvm.node, this.Config.collabvm.displayname, this.screenHidden ? this.screenHiddenThumb : await this.getThumbnail()));
|
||||||
break;
|
break;
|
||||||
@@ -652,7 +678,7 @@ export default class CollabVMServer {
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
renameUser(client: User, newName?: string) {
|
renameUser(client: User, newName?: string, announce: boolean = true) {
|
||||||
// This shouldn't need a ternary but it does for some reason
|
// This shouldn't need a ternary but it does for some reason
|
||||||
var hadName: boolean = client.username ? true : false;
|
var hadName: boolean = client.username ? true : false;
|
||||||
var oldname: any;
|
var oldname: any;
|
||||||
@@ -683,10 +709,13 @@ export default class CollabVMServer {
|
|||||||
client.sendMsg(cvm.guacEncode('rename', '0', status, client.username!, client.rank.toString()));
|
client.sendMsg(cvm.guacEncode('rename', '0', status, client.username!, client.rank.toString()));
|
||||||
if (hadName) {
|
if (hadName) {
|
||||||
this.logger.Info(`Rename ${client.IP.address} from ${oldname} to ${client.username}`);
|
this.logger.Info(`Rename ${client.IP.address} from ${oldname} to ${client.username}`);
|
||||||
this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('rename', '1', oldname, client.username!, client.rank.toString())));
|
if (announce) this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('rename', '1', oldname, client.username!, client.rank.toString())));
|
||||||
} else {
|
} else {
|
||||||
this.logger.Info(`Rename ${client.IP.address} to ${client.username}`);
|
this.logger.Info(`Rename ${client.IP.address} to ${client.username}`);
|
||||||
this.clients.forEach((c) => c.sendMsg(cvm.guacEncode('adduser', '1', client.username!, client.rank.toString())));
|
if (announce) this.clients.forEach((c) => {
|
||||||
|
c.sendMsg(cvm.guacEncode('adduser', '1', client.username!, client.rank.toString()));
|
||||||
|
if (client.countryCode !== null) c.sendMsg(cvm.guacEncode('flag', client.username!, client.countryCode));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -697,6 +726,14 @@ export default class CollabVMServer {
|
|||||||
return cvm.guacEncode(...arr);
|
return cvm.guacEncode(...arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFlagMsg() : string {
|
||||||
|
var arr = ['flag'];
|
||||||
|
for (let c of this.clients.filter(cl => cl.countryCode !== null && cl.username && (!cl.noFlag || cl.rank === Rank.Unregistered))) {
|
||||||
|
arr.push(c.username!, c.countryCode!);
|
||||||
|
}
|
||||||
|
return cvm.guacEncode(...arr);
|
||||||
|
}
|
||||||
|
|
||||||
getChatHistoryMsg(): string {
|
getChatHistoryMsg(): string {
|
||||||
var arr: string[] = ['chat'];
|
var arr: string[] = ['chat'];
|
||||||
this.ChatHistory.forEach((c) => arr.push(c.user, c.msg));
|
this.ChatHistory.forEach((c) => arr.push(c.user, c.msg));
|
||||||
|
|||||||
107
cvmts/src/GeoIPDownloader.ts
Normal file
107
cvmts/src/GeoIPDownloader.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { Logger } from '@cvmts/shared';
|
||||||
|
import { Reader, ReaderModel } from '@maxmind/geoip2-node';
|
||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'node:path';
|
||||||
|
import { Readable } from 'node:stream';
|
||||||
|
import { ReadableStream } from 'node:stream/web';
|
||||||
|
import { finished } from 'node:stream/promises';
|
||||||
|
import { execa } from 'execa';
|
||||||
|
|
||||||
|
export default class GeoIPDownloader {
|
||||||
|
private directory: string;
|
||||||
|
private accountID: string;
|
||||||
|
private licenseKey: string;
|
||||||
|
private logger: Logger
|
||||||
|
constructor(filename: string, accountID: string, licenseKey: string) {
|
||||||
|
this.directory = filename;
|
||||||
|
if (!this.directory.endsWith('/')) this.directory += '/';
|
||||||
|
this.accountID = accountID;
|
||||||
|
this.licenseKey = licenseKey;
|
||||||
|
this.logger = new Logger('CVMTS.GeoIPDownloader');
|
||||||
|
}
|
||||||
|
|
||||||
|
private genAuthHeader(): string {
|
||||||
|
return `Basic ${Buffer.from(`${this.accountID}:${this.licenseKey}`).toString('base64')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensureDirectoryExists(): Promise<void> {
|
||||||
|
let stat;
|
||||||
|
try {
|
||||||
|
stat = await fs.stat(this.directory);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
var error = e as NodeJS.ErrnoException;
|
||||||
|
if (error.code === 'ENOTDIR') {
|
||||||
|
this.logger.Warning('File exists at GeoIP directory path, unlinking...');
|
||||||
|
await fs.unlink(this.directory.substring(0, this.directory.length - 1));
|
||||||
|
} else if (error.code !== 'ENOENT') {
|
||||||
|
this.logger.Error('Failed to access GeoIP directory: {0}', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
this.logger.Info('Creating GeoIP directory: {0}', this.directory);
|
||||||
|
await fs.mkdir(this.directory, { recursive: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGeoIPReader(): Promise<ReaderModel> {
|
||||||
|
await this.ensureDirectoryExists();
|
||||||
|
let dbpath = path.join(this.directory, (await this.getLatestVersion()).replace('.tar.gz', ''), 'GeoLite2-Country.mmdb');
|
||||||
|
try {
|
||||||
|
await fs.access(dbpath, fs.constants.F_OK | fs.constants.R_OK);
|
||||||
|
this.logger.Info('Loading cached GeoIP database: {0}', dbpath);
|
||||||
|
} catch (ex) {
|
||||||
|
var error = ex as NodeJS.ErrnoException;
|
||||||
|
if (error.code === 'ENOENT') {
|
||||||
|
await this.downloadLatestDatabase();
|
||||||
|
} else {
|
||||||
|
this.logger.Error('Failed to access GeoIP database: {0}', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return await Reader.open(dbpath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLatestVersion(): Promise<string> {
|
||||||
|
let res = await fetch('https://download.maxmind.com/geoip/databases/GeoLite2-Country/download?suffix=tar.gz', {
|
||||||
|
redirect: 'follow',
|
||||||
|
method: "HEAD",
|
||||||
|
headers: {
|
||||||
|
"Authorization": this.genAuthHeader()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let disposition = res.headers.get('Content-Disposition');
|
||||||
|
if (!disposition) {
|
||||||
|
this.logger.Error('Failed to get latest version of GeoIP database: No Content-Disposition header');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
let filename = disposition.match(/filename=(.*)$/);
|
||||||
|
if (!filename) {
|
||||||
|
this.logger.Error('Failed to get latest version of GeoIP database: Could not parse version from Content-Disposition header');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
return filename[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
async downloadLatestDatabase(): Promise<void> {
|
||||||
|
let filename = await this.getLatestVersion();
|
||||||
|
this.logger.Info('Downloading latest GeoIP database: {0}', filename);
|
||||||
|
let dbpath = path.join(this.directory, filename);
|
||||||
|
let file = await fs.open(dbpath, fs.constants.O_CREAT | fs.constants.O_TRUNC | fs.constants.O_WRONLY);
|
||||||
|
let stream = file.createWriteStream();
|
||||||
|
let res = await fetch('https://download.maxmind.com/geoip/databases/GeoLite2-Country/download?suffix=tar.gz', {
|
||||||
|
redirect: 'follow',
|
||||||
|
headers: {
|
||||||
|
"Authorization": this.genAuthHeader()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await finished(Readable.fromWeb(res.body as ReadableStream<any>).pipe(stream));
|
||||||
|
await file.close();
|
||||||
|
this.logger.Info('Finished downloading latest GeoIP database: {0}', filename);
|
||||||
|
this.logger.Info('Extracting GeoIP database: {0}', filename);
|
||||||
|
// yeah whatever
|
||||||
|
await execa('tar', ['xzf', filename], {cwd: this.directory});
|
||||||
|
this.logger.Info('Unlinking GeoIP tarball');
|
||||||
|
await fs.unlink(dbpath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,12 @@ export default interface IConfig {
|
|||||||
origin: boolean;
|
origin: boolean;
|
||||||
originAllowedDomains: string[];
|
originAllowedDomains: string[];
|
||||||
};
|
};
|
||||||
|
geoip: {
|
||||||
|
enabled: boolean;
|
||||||
|
directory: string;
|
||||||
|
accountID: string;
|
||||||
|
licenseKey: string;
|
||||||
|
}
|
||||||
tcp: {
|
tcp: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
host: string;
|
host: string;
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ export class User {
|
|||||||
msgsSent: number;
|
msgsSent: number;
|
||||||
Config: IConfig;
|
Config: IConfig;
|
||||||
IP: IPData;
|
IP: IPData;
|
||||||
|
// Hide flag. Only takes effect if the user is logged in.
|
||||||
|
noFlag: boolean = false;
|
||||||
|
countryCode: string | null = null;
|
||||||
// Rate limiters
|
// Rate limiters
|
||||||
ChatRateLimit: RateLimiter;
|
ChatRateLimit: RateLimiter;
|
||||||
LoginRateLimit: RateLimiter;
|
LoginRateLimit: RateLimiter;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { User } from './User.js';
|
|||||||
import TCPServer from './TCP/TCPServer.js';
|
import TCPServer from './TCP/TCPServer.js';
|
||||||
import VM from './VM.js';
|
import VM from './VM.js';
|
||||||
import VNCVM from './VNCVM/VNCVM.js';
|
import VNCVM from './VNCVM/VNCVM.js';
|
||||||
|
import GeoIPDownloader from './GeoIPDownloader.js';
|
||||||
|
|
||||||
let logger = new Shared.Logger('CVMTS.Init');
|
let logger = new Shared.Logger('CVMTS.Init');
|
||||||
|
|
||||||
@@ -44,6 +45,11 @@ async function stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function start() {
|
async function start() {
|
||||||
|
let geoipReader = null;
|
||||||
|
if (Config.geoip.enabled) {
|
||||||
|
let downloader = new GeoIPDownloader(Config.geoip.directory, Config.geoip.accountID, Config.geoip.licenseKey);
|
||||||
|
geoipReader = await downloader.getGeoIPReader();
|
||||||
|
}
|
||||||
// Init the auth manager if enabled
|
// Init the auth manager if enabled
|
||||||
let auth = Config.auth.enabled ? new AuthManager(Config.auth.apiEndpoint, Config.auth.secretKey) : null;
|
let auth = Config.auth.enabled ? new AuthManager(Config.auth.apiEndpoint, Config.auth.secretKey) : null;
|
||||||
switch (Config.vm.type) {
|
switch (Config.vm.type) {
|
||||||
@@ -82,7 +88,7 @@ async function start() {
|
|||||||
|
|
||||||
await VM.Start();
|
await VM.Start();
|
||||||
// Start up the server
|
// Start up the server
|
||||||
var CVM = new CollabVMServer(Config, VM, auth);
|
var CVM = new CollabVMServer(Config, VM, auth, geoipReader);
|
||||||
|
|
||||||
var WS = new WSServer(Config);
|
var WS = new WSServer(Config);
|
||||||
WS.on('connect', (client: User) => CVM.addUser(client));
|
WS.on('connect', (client: User) => CVM.addUser(client));
|
||||||
|
|||||||
103
yarn.lock
103
yarn.lock
@@ -61,6 +61,7 @@ __metadata:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@cvmts/cvm-rs": "npm:*"
|
"@cvmts/cvm-rs": "npm:*"
|
||||||
"@cvmts/qemu": "npm:*"
|
"@cvmts/qemu": "npm:*"
|
||||||
|
"@maxmind/geoip2-node": "npm:^5.0.0"
|
||||||
"@types/node": "npm:^20.12.5"
|
"@types/node": "npm:^20.12.5"
|
||||||
"@types/ws": "npm:^8.5.5"
|
"@types/ws": "npm:^8.5.5"
|
||||||
execa: "npm:^8.0.1"
|
execa: "npm:^8.0.1"
|
||||||
@@ -351,6 +352,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@maxmind/geoip2-node@npm:^5.0.0":
|
||||||
|
version: 5.0.0
|
||||||
|
resolution: "@maxmind/geoip2-node@npm:5.0.0"
|
||||||
|
dependencies:
|
||||||
|
ip6addr: "npm:^0.2.5"
|
||||||
|
maxmind: "npm:^4.2.0"
|
||||||
|
checksum: 10c0/10f6c936b45632210210750b839578c610a3ceba06aff5db2a3d9da68b51b986caa7e700c78ab2ea02524b3793e4f21daee7ecfde1dc242241291e43833b7087
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@mischnic/json-sourcemap@npm:^0.1.0":
|
"@mischnic/json-sourcemap@npm:^0.1.0":
|
||||||
version: 0.1.1
|
version: 0.1.1
|
||||||
resolution: "@mischnic/json-sourcemap@npm:0.1.1"
|
resolution: "@mischnic/json-sourcemap@npm:0.1.1"
|
||||||
@@ -1648,6 +1659,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"assert-plus@npm:1.0.0, assert-plus@npm:^1.0.0":
|
||||||
|
version: 1.0.0
|
||||||
|
resolution: "assert-plus@npm:1.0.0"
|
||||||
|
checksum: 10c0/b194b9d50c3a8f872ee85ab110784911e696a4d49f7ee6fc5fb63216dedbefd2c55999c70cb2eaeb4cf4a0e0338b44e9ace3627117b5bf0d42460e9132f21b91
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"balanced-match@npm:^1.0.0":
|
"balanced-match@npm:^1.0.0":
|
||||||
version: 1.0.2
|
version: 1.0.2
|
||||||
resolution: "balanced-match@npm:1.0.2"
|
resolution: "balanced-match@npm:1.0.2"
|
||||||
@@ -1887,6 +1905,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"core-util-is@npm:1.0.2":
|
||||||
|
version: 1.0.2
|
||||||
|
resolution: "core-util-is@npm:1.0.2"
|
||||||
|
checksum: 10c0/980a37a93956d0de8a828ce508f9b9e3317039d68922ca79995421944146700e4aaf490a6dbfebcb1c5292a7184600c7710b957d724be1e37b8254c6bc0fe246
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"cosmiconfig@npm:^8.0.0":
|
"cosmiconfig@npm:^8.0.0":
|
||||||
version: 8.3.6
|
version: 8.3.6
|
||||||
resolution: "cosmiconfig@npm:8.3.6"
|
resolution: "cosmiconfig@npm:8.3.6"
|
||||||
@@ -2206,6 +2231,20 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"extsprintf@npm:1.3.0":
|
||||||
|
version: 1.3.0
|
||||||
|
resolution: "extsprintf@npm:1.3.0"
|
||||||
|
checksum: 10c0/f75114a8388f0cbce68e277b6495dc3930db4dde1611072e4a140c24e204affd77320d004b947a132e9a3b97b8253017b2b62dce661975fb0adced707abf1ab5
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"extsprintf@npm:^1.2.0":
|
||||||
|
version: 1.4.1
|
||||||
|
resolution: "extsprintf@npm:1.4.1"
|
||||||
|
checksum: 10c0/e10e2769985d0e9b6c7199b053a9957589d02e84de42832c295798cb422a025e6d4a92e0259c1fb4d07090f5bfde6b55fd9f880ac5855bd61d775f8ab75a7ab0
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"fd-slicer@npm:~1.1.0":
|
"fd-slicer@npm:~1.1.0":
|
||||||
version: 1.1.0
|
version: 1.1.0
|
||||||
resolution: "fd-slicer@npm:1.1.0"
|
resolution: "fd-slicer@npm:1.1.0"
|
||||||
@@ -2500,6 +2539,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"ip6addr@npm:^0.2.5":
|
||||||
|
version: 0.2.5
|
||||||
|
resolution: "ip6addr@npm:0.2.5"
|
||||||
|
dependencies:
|
||||||
|
assert-plus: "npm:^1.0.0"
|
||||||
|
jsprim: "npm:^2.0.2"
|
||||||
|
checksum: 10c0/aaa16f844d57d2c8afca375dabb42a62e6990ea044e397bf50e18bea8b445ae0978df6fae5898c898edfd6b58cc3d3c557f405a34792739be912cd303563a916
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"is-arrayish@npm:^0.2.1":
|
"is-arrayish@npm:^0.2.1":
|
||||||
version: 0.2.1
|
version: 0.2.1
|
||||||
resolution: "is-arrayish@npm:0.2.1"
|
resolution: "is-arrayish@npm:0.2.1"
|
||||||
@@ -2633,6 +2682,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"json-schema@npm:0.4.0":
|
||||||
|
version: 0.4.0
|
||||||
|
resolution: "json-schema@npm:0.4.0"
|
||||||
|
checksum: 10c0/d4a637ec1d83544857c1c163232f3da46912e971d5bf054ba44fdb88f07d8d359a462b4aec46f2745efbc57053365608d88bc1d7b1729f7b4fc3369765639ed3
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"json5@npm:^2.2.0, json5@npm:^2.2.1":
|
"json5@npm:^2.2.0, json5@npm:^2.2.1":
|
||||||
version: 2.2.3
|
version: 2.2.3
|
||||||
resolution: "json5@npm:2.2.3"
|
resolution: "json5@npm:2.2.3"
|
||||||
@@ -2642,6 +2698,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"jsprim@npm:^2.0.2":
|
||||||
|
version: 2.0.2
|
||||||
|
resolution: "jsprim@npm:2.0.2"
|
||||||
|
dependencies:
|
||||||
|
assert-plus: "npm:1.0.0"
|
||||||
|
extsprintf: "npm:1.3.0"
|
||||||
|
json-schema: "npm:0.4.0"
|
||||||
|
verror: "npm:1.10.0"
|
||||||
|
checksum: 10c0/677be2d41df536c92c6d0114a492ef197084018cfbb1a3e10b1fa1aad889564b2e3a7baa6af7949cc2d73678f42368b0be165a26bd4e4de6883a30dd6a24e98d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"just-install@npm:^2.0.1":
|
"just-install@npm:^2.0.1":
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
resolution: "just-install@npm:2.0.1"
|
resolution: "just-install@npm:2.0.1"
|
||||||
@@ -2832,6 +2900,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"maxmind@npm:^4.2.0":
|
||||||
|
version: 4.3.20
|
||||||
|
resolution: "maxmind@npm:4.3.20"
|
||||||
|
dependencies:
|
||||||
|
mmdb-lib: "npm:2.1.1"
|
||||||
|
tiny-lru: "npm:11.2.6"
|
||||||
|
checksum: 10c0/f21b366f7c2bf7f6853eeea52478e53dd1052ad75f6f45c270258d5ff023c5f4a85c577d6b1ebdb0a8734073e24df5ed66375cdac0c3159a8f8ae30c6535149d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"mdn-data@npm:2.0.14":
|
"mdn-data@npm:2.0.14":
|
||||||
version: 2.0.14
|
version: 2.0.14
|
||||||
resolution: "mdn-data@npm:2.0.14"
|
resolution: "mdn-data@npm:2.0.14"
|
||||||
@@ -2965,6 +3043,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"mmdb-lib@npm:2.1.1":
|
||||||
|
version: 2.1.1
|
||||||
|
resolution: "mmdb-lib@npm:2.1.1"
|
||||||
|
checksum: 10c0/675817303af64c21be02e9550ce885b6ffcc6fbbeae7959a189493ccf68c6b7bac74afa00376fd7a421ff2acd8f74f44fc7fd25aeed0675fc21dbc1a9d5df9f9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"mnemonist@npm:^0.39.5":
|
"mnemonist@npm:^0.39.5":
|
||||||
version: 0.39.8
|
version: 0.39.8
|
||||||
resolution: "mnemonist@npm:0.39.8"
|
resolution: "mnemonist@npm:0.39.8"
|
||||||
@@ -3760,6 +3845,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"tiny-lru@npm:11.2.6":
|
||||||
|
version: 11.2.6
|
||||||
|
resolution: "tiny-lru@npm:11.2.6"
|
||||||
|
checksum: 10c0/d59b2047edae1b4b79708070463ed27ddb1daa64563b74eedaa571e555c47f8de3a7cc19171f47dc46c01f1b7283d9afd2c682dddb4832552ed747d52cd297a6
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"to-regex-range@npm:^5.0.1":
|
"to-regex-range@npm:^5.0.1":
|
||||||
version: 5.0.1
|
version: 5.0.1
|
||||||
resolution: "to-regex-range@npm:5.0.1"
|
resolution: "to-regex-range@npm:5.0.1"
|
||||||
@@ -3876,6 +3968,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"verror@npm:1.10.0":
|
||||||
|
version: 1.10.0
|
||||||
|
resolution: "verror@npm:1.10.0"
|
||||||
|
dependencies:
|
||||||
|
assert-plus: "npm:^1.0.0"
|
||||||
|
core-util-is: "npm:1.0.2"
|
||||||
|
extsprintf: "npm:^1.2.0"
|
||||||
|
checksum: 10c0/37ccdf8542b5863c525128908ac80f2b476eed36a32cb944de930ca1e2e78584cc435c4b9b4c68d0fc13a47b45ff364b4be43aa74f8804f9050140f660fb660d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"weak-lru-cache@npm:^1.2.2":
|
"weak-lru-cache@npm:^1.2.2":
|
||||||
version: 1.2.2
|
version: 1.2.2
|
||||||
resolution: "weak-lru-cache@npm:1.2.2"
|
resolution: "weak-lru-cache@npm:1.2.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user