2.0.5 - first stable version of gateway

This commit is contained in:
Dragurimu 2022-08-31 08:59:53 +08:00
parent 9d4b543c8b
commit a39c06bab9
26 changed files with 710 additions and 1210 deletions

1
.gitignore vendored
View File

@ -22,6 +22,7 @@ packages/core/docs.json
node_modules
.pnp
.pnp.js
.vscode
# testing
coverage

View File

@ -1,6 +1,6 @@
# @biscuitland/api-types
## 2.0.4
## 2.0.5
### Major Changes

View File

@ -25,7 +25,7 @@ import type { DiscordUser } from '@biscuitland/api-types';
## Example for [Deno](https://deno.land/)
```ts
import type { DiscordUser } from "https://unpkg.com/@biscuitland/api-types@2.0.4/dist/index.d.ts";
import type { DiscordUser } from "https://unpkg.com/@biscuitland/api-types@2.0.5/dist/index.d.ts";
```
We deliver this package through [unpkg](https://unpkg.com/) and it does contain constants and routes too

View File

@ -1,6 +1,6 @@
{
"name": "@biscuitland/api-types",
"version": "2.0.4",
"version": "2.0.5",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",

View File

@ -5,7 +5,7 @@ export const BASE_URL = 'https://discord.com/api';
export const API_VERSION = 10;
/** https://github.com/oasisjs/biscuit/releases */
export const BISCUIT_VERSION = '2.0.4';
export const BISCUIT_VERSION = '2.0.5';
/** https://discord.com/developers/docs/reference#user-agent */
export const USER_AGENT = `DiscordBot (https://github.com/oasisjs/biscuit, v${BISCUIT_VERSION})`;

View File

@ -1,6 +1,6 @@
# @biscuitland/cache
## 2.0.4
## 2.0.5
### Major Changes
@ -9,4 +9,4 @@
### Patch Changes
- Updated dependencies
- @biscuitland/api-types@2.0.4
- @biscuitland/api-types@2.0.5

View File

@ -1,6 +1,6 @@
{
"name": "@biscuitland/cache",
"version": "2.0.4",
"version": "2.0.5",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
@ -23,7 +23,7 @@
}
},
"dependencies": {
"@biscuitland/api-types": "^2.0.4",
"@biscuitland/api-types": "^2.0.5",
"ioredis": "^5.2.2"
},
"devDependencies": {

View File

@ -1,6 +1,6 @@
# @biscuitland/core
## 2.0.4
## 2.0.5
### Major Changes
@ -9,6 +9,6 @@
### Patch Changes
- Updated dependencies
- @biscuitland/api-types@2.0.4
- @biscuitland/rest@2.0.4
- @biscuitland/ws@2.0.4
- @biscuitland/api-types@2.0.5
- @biscuitland/rest@2.0.5
- @biscuitland/ws@2.0.5

View File

@ -1,6 +1,6 @@
{
"name": "@biscuitland/core",
"version": "2.0.4",
"version": "2.0.5",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
@ -23,9 +23,9 @@
}
},
"dependencies": {
"@biscuitland/api-types": "^2.0.4",
"@biscuitland/rest": "^2.0.4",
"@biscuitland/ws": "^2.0.4"
"@biscuitland/api-types": "^2.0.5",
"@biscuitland/rest": "^2.0.5",
"@biscuitland/ws": "^2.0.5"
},
"devDependencies": {
"tsup": "^6.1.3"

View File

@ -10,13 +10,11 @@ import type {
Localization,
Snowflake,
DiscordGetGatewayBot,
DiscordGatewayPayload
} from '@biscuitland/api-types';
import { ApplicationCommandTypes, GatewayOpcodes } from '@biscuitland/api-types';
import { ApplicationCommandTypes, GatewayOpcodes ,
// routes
import {
APPLICATION_COMMANDS,
GUILD_APPLICATION_COMMANDS,
GUILD_APPLICATION_COMMANDS_PERMISSIONS,
@ -24,6 +22,8 @@ import {
USER
} from '@biscuitland/api-types';
// routes
import type { PermissionResolvable } from './structures/special/permissions';
import type { Activities, StatusTypes } from './structures/presence';
@ -36,14 +36,13 @@ import { User } from './structures/user';
import type { RestAdapter } from '@biscuitland/rest';
import { DefaultRestAdapter } from '@biscuitland/rest';
import type { WsAdapter } from '@biscuitland/ws';
import { DefaultWsAdapter } from '@biscuitland/ws';
import type { Shard } from '@biscuitland/ws';
import { ShardManager } from '@biscuitland/ws';
import type { EventAdapter } from './adapters/event-adapter';
import { DefaultEventAdapter } from './adapters/default-event-adapter';
import { Util } from './utils/util';
import { Shard } from '@biscuitland/ws';
// PRESENCE
@ -146,7 +145,7 @@ export interface BiscuitOptions {
};
ws: {
adapter?: { new (...args: any[]): WsAdapter };
adapter?: { new (...args: any[]): ShardManager };
options: any;
};
}
@ -180,9 +179,9 @@ export class Session {
options: null,
},
ws: {
adapter: DefaultWsAdapter,
adapter: ShardManager,
options: null,
},
}
};
options: BiscuitOptions;
@ -190,7 +189,7 @@ export class Session {
readonly events: EventAdapter;
readonly rest: RestAdapter;
readonly ws: WsAdapter;
readonly ws: ShardManager;
private adapters = new Map<string, any>();
@ -210,20 +209,17 @@ export class Session {
// makeWs
const defHandler: DiscordRawEventHandler = (shard, event) => {
let data = event as any;
// let data = JSON.parse(message) as DiscordGatewayPayload;
const defHandler = (shard: Shard, payload: DiscordGatewayPayload) => {
Actions.raw(this, shard.options.id, payload);
Actions.raw(this, shard.id, data);
if (!data.t || !data.d) {
if (!payload.t || !payload.d) {
return;
}
Actions[data.t as keyof typeof Actions]?.(
Actions[payload.t as keyof typeof Actions]?.(
this,
shard.id,
data.d as any
shard.options.id,
payload.d as any
);
};
@ -231,21 +227,28 @@ export class Session {
this.options.ws.options = {
handleDiscordPayload: defHandler,
gatewayConfig: {
token: this.options.token,
intents: this.options.intents,
},
gateway: {
url: '',
shards: '',
intents: this.options.intents,
token: this.options.token,
session_start_limit: {
total: 1000,
remaining: 1000,
reset_after: 3600000,
max_concurrency: 1
}
},
config: {
token: this.options.token,
intents: this.options.intents,
},
};
}
// makeEvents
this.events = this.options.events?.adapter
? new this.options.events.adapter()
: new DefaultEventAdapter();
this.events = this.options.events?.adapter ? new this.options.events.adapter() : new DefaultEventAdapter();
this.ws = this.getWs();
this.token = options.token;
@ -282,7 +285,7 @@ export class Session {
* @inheritDoc
*/
private getWs(): WsAdapter {
private getWs(): ShardManager {
return this.getAdapter(
this.options.ws.adapter!,
this.options.ws.options
@ -294,23 +297,10 @@ export class Session {
*/
async start(): Promise<void> {
const nonParsed = await this.rest.get<DiscordGetGatewayBot>('/gateway/bot');
const gateway = await this.rest.get<DiscordGetGatewayBot>('/gateway/bot');
this.ws.options.gateway = gateway;
this.ws.options.gatewayBot = {
url: nonParsed.url,
shards: nonParsed.shards,
sessionStartLimit: {
total: nonParsed.session_start_limit.total,
remaining: nonParsed.session_start_limit.remaining,
resetAfter: nonParsed.session_start_limit.reset_after,
maxConcurrency: nonParsed.session_start_limit.max_concurrency,
},
};
this.ws.options.lastShardId = this.ws.options.gatewayBot.shards - 1;
this.ws.agent.options.totalShards = this.ws.options.gatewayBot.shards;
this.ws.shards();
this.ws.spawns();
}
// USEFUL METHODS
@ -338,7 +328,7 @@ export class Session {
* }
*/
editStatus(shardId: number, status: StatusUpdate, prio = true): void {
const shard = this.ws.agent.shards.get(shardId);
const shard = this.ws.shards.get(shardId);
if (!shard) {
throw new Error(`Unknown shard ${shardId}`);
@ -350,7 +340,7 @@ export class Session {
status: status.status,
since: null,
afk: false,
activities: status.activities.map((activity) => {
activities: status.activities.map(activity => {
return {
name: activity.name,
type: activity.type,
@ -399,7 +389,7 @@ export class Session {
permissions: options,
},
{
headers: { 'Authorization': `Bearer ${bearerToken}` },
headers: { Authorization: `Bearer ${bearerToken}` },
},
);
}
@ -463,7 +453,7 @@ export class Session {
guildId
? GUILD_APPLICATION_COMMANDS(this.applicationId, guildId)
: APPLICATION_COMMANDS(this.applicationId),
options.map((o) =>
options.map(o =>
this.isContextApplicationCommand(o)
? {
name: o.name,

View File

@ -1,9 +1,9 @@
# @biscuitland/helpers
## 2.0.4
## 2.0.5
### Patch Changes
- Updated dependencies
- @biscuitland/api-types@2.0.4
- @biscuitland/core@2.0.4
- @biscuitland/api-types@2.0.5
- @biscuitland/core@2.0.5

View File

@ -1,6 +1,6 @@
{
"name": "@biscuitland/helpers",
"version": "2.0.4",
"version": "2.0.5",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
@ -23,8 +23,8 @@
}
},
"dependencies": {
"@biscuitland/api-types": "^2.0.4",
"@biscuitland/core": "^2.0.4"
"@biscuitland/api-types": "^2.0.5",
"@biscuitland/core": "^2.0.5"
},
"devDependencies": {
"tsup": "^6.1.3"

View File

@ -1,6 +1,6 @@
# @biscuitland/rest
## 2.0.4
## 2.0.5
### Major Changes
@ -9,4 +9,4 @@
### Patch Changes
- Updated dependencies
- @biscuitland/api-types@2.0.4
- @biscuitland/api-types@2.0.5

View File

@ -1,6 +1,6 @@
{
"name": "@biscuitland/rest",
"version": "2.0.4",
"version": "2.0.5",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
@ -23,7 +23,7 @@
}
},
"dependencies": {
"@biscuitland/api-types": "^2.0.4"
"@biscuitland/api-types": "^2.0.5"
},
"devDependencies": {
"tsup": "^6.1.3"

View File

@ -1,6 +1,6 @@
# @biscuitland/ws
## 2.0.4
## 2.0.5
### Major Changes
@ -9,4 +9,4 @@
### Patch Changes
- Updated dependencies
- @biscuitland/api-types@2.0.4
- @biscuitland/api-types@2.0.5

View File

@ -1,6 +1,6 @@
{
"name": "@biscuitland/ws",
"version": "2.0.4",
"version": "2.0.5",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
@ -23,7 +23,8 @@
}
},
"dependencies": {
"@biscuitland/api-types": "^2.0.4",
"@biscuitland/api-types": "^2.0.5",
"@sapphire/async-queue": "^1.5.0",
"ws": "^8.8.1"
},
"devDependencies": {

View File

@ -1,234 +0,0 @@
import { createLeakyBucket } from '../utils/bucket-util';
import type { LeakyBucket } from '../utils/bucket-util';
import type { GatewayBot, PickPartial } from '@biscuitland/api-types';
import type { WsAdapter } from './ws-adapter';
import type {
Shard,
ShardGatewayConfig,
ShardOptions,
} from '../services/shard';
import { Agent } from '../services/agent';
export class DefaultWsAdapter implements WsAdapter {
static readonly DEFAULTS = {
spawnShardDelay: 5300,
shardsPerWorker: 25,
totalWorkers: 4,
gatewayBot: {
url: 'wss://gateway.discord.gg',
shards: 1,
sessionStartLimit: {
total: 1000,
remaining: 1000,
resetAfter: 0,
maxConcurrency: 1,
},
},
firstShardId: 0,
lastShardId: 1,
};
buckets = new Map<
number,
{
workers: { id: number; queue: number[] }[];
leak: LeakyBucket;
}
>();
options: Options;
agent: Agent;
constructor(options: DefaultWsOptions) {
this.options = Object.assign(Object.create(DefaultWsAdapter.DEFAULTS), options);
this.options.firstShardId = this.options.firstShardId ?? 0;
this.options.lastShardId = this.options.lastShardId ?? this.options.totalShards - 1 ?? 1;
this.agent = new Agent({
totalShards: this.options.totalShards ?? this.options.gatewayBot.shards ?? 1,
gatewayConfig: this.options.gatewayConfig,
createShardOptions: this.options.createShardOptions,
handleMessage: (shard: Shard, message: MessageEvent<any>) => {
return this.options.handleDiscordPayload(shard, message);
},
handleIdentify: (id: number) => {
return this.buckets.get(id % this.options.gatewayBot.sessionStartLimit.maxConcurrency)!.leak.acquire(1);
},
});
}
/**
* @inheritDoc
*/
prepareBuckets() {
for (
let i = 0;
i < this.options.gatewayBot.sessionStartLimit.maxConcurrency;
++i
) {
this.buckets.set(i, {
workers: [],
leak: createLeakyBucket({
max: 1,
refillAmount: 1,
refillInterval: this.options.spawnShardDelay,
}),
});
}
for (
let shardId = this.options.firstShardId;
shardId <= this.options.lastShardId;
++shardId
) {
if (shardId >= this.agent.options.totalShards) {
throw new Error(
`Shard (id: ${shardId}) is bigger or equal to the used amount of used shards which is ${this.agent.options.totalShards}`
);
}
const bucketId = shardId % this.options.gatewayBot.sessionStartLimit.maxConcurrency;
const bucket = this.buckets.get(bucketId);
if (!bucket) {
throw new Error(
`Shard (id: ${shardId}) got assigned to an illegal bucket id: ${bucketId}, expected a bucket id between 0 and ${
this.options.gatewayBot.sessionStartLimit
.maxConcurrency - 1
}`
);
}
const workerId = this.workerId(shardId);
const worker = bucket.workers.find(w => w.id === workerId);
if (worker) {
worker.queue.push(shardId);
} else {
bucket.workers.push({ id: workerId, queue: [shardId] });
}
}
}
/**
* @inheritDoc
*/
prepareShards() {
this.buckets.forEach((bucket, bucketId) => {
for (const worker of bucket.workers) {
for (const shardId of worker.queue) {
this.workerToIdentify(worker.id, shardId, bucketId);
}
}
});
}
/**
* @inheritDoc
*/
calculateTotalShards(): number {
if (this.agent.options.totalShards < 100) {
return this.agent.options.totalShards;
}
return (
Math.ceil(
this.agent.options.totalShards /
(this.options.gatewayBot.sessionStartLimit
.maxConcurrency === 1
? 16
: this.options.gatewayBot.sessionStartLimit
.maxConcurrency)
) * this.options.gatewayBot.sessionStartLimit.maxConcurrency
);
}
/**
* @inheritDoc
*/
workerToIdentify(_workerId: number, shardId: number, _bucketId: number) {
return this.agent.identify(shardId);
}
/**
* @inheritDoc
*/
workerId(shardId: number) {
let workerId = Math.floor(shardId / this.options.shardsPerWorker);
if (workerId >= this.options.totalWorkers) {
workerId = this.options.totalWorkers - 1;
}
return workerId;
}
/**
* @inheritDoc
*/
shards() {
this.prepareBuckets();
this.prepareShards();
}
}
export type DefaultWsOptions = Pick<
Options,
Exclude<keyof Options, keyof typeof DefaultWsAdapter.DEFAULTS>
> &
Partial<Options>;
interface Options {
/** Delay in milliseconds to wait before spawning next shard. */
spawnShardDelay: number;
/** The amount of shards to load per worker. */
shardsPerWorker: number;
/** The total amount of workers to use for your bot. */
totalWorkers: number;
/** Total amount of shards your bot uses. Useful for zero-downtime updates or resharding. */
totalShards: number;
/** Id of the first Shard which should get controlled by this manager. */
firstShardId: number;
/** Id of the last Shard which should get controlled by this manager. */
lastShardId: number;
createShardOptions?: Omit<
ShardOptions,
'id' | 'totalShards' | 'requestIdentify' | 'gatewayConfig'
>;
/** Important data which is used by the manager to connect shards to the gateway. */
gatewayBot: GatewayBot;
// REMOVE THIS
gatewayConfig: PickPartial<ShardGatewayConfig, 'token'>;
/** Sends the discord payload to another guild. */
handleDiscordPayload: (shard: Shard, data: MessageEvent<any>) => any;
}

View File

@ -1,29 +0,0 @@
import type { Agent } from '../services/agent';
import type { GatewayBot } from '@biscuitland/api-types';
export interface WsAdapter {
options: Partial<Options | any>;
/**
* @inheritDoc
*/
agent: Agent;
/**
* @inheritDoc
*/
shards(): void;
}
interface Options {
/** Id of the first Shard which should get controlled by this manager. */
firstShardId: number;
/** Id of the last Shard which should get controlled by this manager. */
lastShardId: number;
/** Important data which is used by the manager to connect shards to the gateway. */
gatewayBot: GatewayBot;
}

View File

@ -1,9 +1,2 @@
export { WsAdapter } from './adapters/ws-adapter';
export {
DefaultWsAdapter,
DefaultWsOptions,
} from './adapters/default-ws-adapter';
export { AgentOptions, Agent } from './services/agent';
export { ShardManager } from './services/shard-manager';
export { Shard } from './services/shard';

View File

@ -1,113 +0,0 @@
import type { ShardGatewayConfig, ShardOptions } from './shard';
import type { PickPartial } from '@biscuitland/api-types';
import { Shard } from './shard';
export class Agent {
static readonly DEFAULTS = {
shardIds: [],
totalShards: 1,
};
options: Options;
shards: Map<number, Shard>;
constructor(options: AgentOptions) {
this.options = Object.assign(Agent.DEFAULTS, options);
const { handleIdentify } = this.options;
this.shards = new Map(
this.options.shardIds.map(id => {
const shard = new Shard({
id,
totalShards: this.options.totalShards,
gatewayConfig: this.options.gatewayConfig,
handleMessage: (shard, message) => {
return this.options.handleMessage(shard, message);
},
async handleIdentify() {
return await handleIdentify(id);
},
...this.options.createShardOptions,
});
return [id, shard] as const;
})
);
}
/**
* @inheritDoc
*/
async identify(id: number) {
// @ts-ignore
let shard = this.shards.get(id);
if (!shard) {
const { handleIdentify } = this.options;
shard = new Shard({
id,
totalShards: this.options.totalShards,
gatewayConfig: this.options.gatewayConfig,
handleMessage: (shard, message) => {
return this.options.handleMessage(shard, message);
},
async handleIdentify() {
return await handleIdentify(id);
},
...this.options.createShardOptions,
});
this.shards.set(id, shard);
}
return await shard.identify();
}
/**
* @inheritDoc
*/
async scale() {
//
}
}
export type AgentOptions = Pick<
Options,
Exclude<keyof Options, keyof typeof Agent.DEFAULTS>
> &
Partial<Options>;
interface Options {
/** Ids of the Shards which should be managed. */
shardIds: number[];
/** Total amount of Shard used by the bot. */
totalShards: number;
/** Options which are used to create a new Shard. */
createShardOptions?: Omit<
ShardOptions,
'id' | 'totalShards' | 'requestIdentify' | 'gatewayConfig'
>;
/** Gateway configuration which is used when creating a Shard. */
gatewayConfig: PickPartial<ShardGatewayConfig, 'token'>;
/** Sends the discord payload to another guild. */
handleMessage: (shard: Shard, data: MessageEvent<any>) => any;
/** This function communicates with the parent manager. */
handleIdentify(shardId: number): Promise<void>;
}

View File

@ -0,0 +1,121 @@
import type { ShardManagerOptions, SMO } from '../types';
import type { LeakyBucket } from '../utils/bucket';
import { Shard } from './shard';
import { createLeakyBucket } from '../utils/bucket';
import { Options } from '../utils/options';
export class ShardManager {
static readonly DEFAULTS = {
workers: {
shards: 25,
amount: 5,
delay: 5000
},
shards: {
timeout: 15000,
delay: 5000
}
};
readonly options: SMO;
readonly buckets = new Map<
number,
{
workers: { id: number; queue: number[] }[];
leak: LeakyBucket;
}
>();
readonly shards = new Map<number, Shard>();
constructor(options: ShardManagerOptions) {
this.options = Options({}, ShardManager.DEFAULTS, options);
}
/** Invokes internal processing and respawns shards */
async respawns(): Promise<void> {
//
}
/** Invoke internal processing and spawns shards */
async spawns(): Promise<void> {
const { gateway, workers } = this.options;
/** Creates the necessary buckets according to concurrency */
for (let i = 0; i < gateway.session_start_limit.max_concurrency; i++) {
this.buckets.set(i, {
workers: [],
leak: createLeakyBucket({
max: 1,
refillAmount: 1,
refillInterval: workers.delay,
}),
});
}
/** Create the start sequence of the shards inside the buckets. */
for (let i = 0; i < gateway.shards; i++) {
const bucketID = i % gateway.session_start_limit.max_concurrency;
const bucket = this.buckets.get(bucketID);
if (bucket) {
const workerID = Math.floor(i / workers.shards);
const worker = bucket.workers.find(w => w.id === workerID);
if (worker) {
worker.queue.push(i);
} else {
bucket.workers.push({ id: workerID, queue: [i] });
}
}
}
/** Route all shards to workers */
this.buckets.forEach(async bucket => {
for (const worker of bucket.workers) {
for (const id of worker.queue) {
await this.connect(id);
}
}
});
}
/** Invokes the bucket to prepare the connection to the shard */
private async connect(id: number): Promise<Shard> {
const { gateway } = this.options;
let shard = this.shards.get(id);
if (!shard) {
shard = new Shard({
id,
gateway: this.options.gateway,
shards: this.options.shards,
config: this.options.config,
handlePayloads: async (shard, payload) => {
await this.options.handleDiscordPayload(shard, payload); // remove await?
},
handleIdentify: async (id: number) => {
await this.buckets.get(id % gateway.session_start_limit.max_concurrency)!.leak.acquire(1); // remove await?
}
});
this.shards.set(id, shard);
}
await shard.connect();
return shard;
}
}

File diff suppressed because it is too large Load Diff

91
packages/ws/src/types.ts Normal file
View File

@ -0,0 +1,91 @@
import type { DiscordGatewayPayload, DiscordGetGatewayBot, GatewayIntents } from '@biscuitland/api-types';
import type { ShardManager } from './services/shard-manager';
import type { Shard } from './services/shard';
/** ShardManager */
export type ShardManagerOptions = Pick<SMO, Exclude<keyof SMO, keyof typeof ShardManager.DEFAULTS>> & Partial<SMO>;
export interface SMO {
/** Function for interpretation of messages from discord */
handleDiscordPayload: (shard: Shard, payload: DiscordGatewayPayload) => unknown;
/** Based on the information in Get Gateway */
gateway: DiscordGetGatewayBot;
/** Workers options */
workers: ShardManagerWorkersOptions;
/** Authentication */
config: {
intents?: GatewayIntents;
token: string;
};
/** Options shards */
shards: ShardManagerShardsOptions;
}
export interface ShardManagerWorkersOptions {
/**
* Number of shards per worker
* @default 25
*/
shards: number;
/**
* Number of workers
* @default 5
*/
amount: number;
/**
* Waiting time between workers
* @default 5000
*/
delay: number;
}
export interface ShardManagerShardsOptions {
/**
* Waiting time to receive the ready event.
* @default 15000
*/
timeout: number;
/**
* Waiting time between shards
* @default 5000
*/
delay: number;
}
/** Shard */
export type ShardOptions = Pick<SO, Exclude<keyof SO, keyof typeof Shard.DEFAULTS>> & Partial<SO>;
export interface SO {
/** Shard Id */
id: number;
/** Based on the information in Get Gateway */
gateway: DiscordGetGatewayBot;
/** Options shards */
shards: ShardManagerShardsOptions;
/** Authentication */
config: {
intents?: GatewayIntents;
token: string;
};
/** Function for interpretation of messages from discord */
handlePayloads: (shard: Shard, data: DiscordGatewayPayload) => Promise<void>;
/** Notify the manager if the shard is ready. */
handleIdentify: (id: number) => Promise<void>;
}
export type ShardStatus = 'Disconnected' | 'Handshaking' | 'Connecting' | 'Heartbeating' | 'Identifying' | 'Resuming' | 'Ready';

View File

@ -1,4 +1,4 @@
/** unnecessary */
/** Create from scratch */
import type { PickPartial } from '@biscuitland/api-types';

View File

@ -0,0 +1,42 @@
const isPlainObject = (value: any) => {
return (
value !== null
&& typeof value === 'object'
&& typeof value.constructor === 'function'
// eslint-disable-next-line no-prototype-builtins
&& (value.constructor.prototype.hasOwnProperty('isPrototypeOf') || Object.getPrototypeOf(value.constructor.prototype) === null)
)
|| (value && Object.getPrototypeOf(value) === null);
};
const isObject = (o: any) => {
return !!o && typeof o === 'object' && !Array.isArray(o);
};
export const Options = (defaults: any, ...options: any[]): any => {
if (!options.length) {
return defaults;
}
const source = options.shift();
if (isObject(defaults) && isPlainObject(source)) {
Object.entries(source).forEach(([key, value]) => {
if (typeof value === 'undefined') {
return;
}
if (isPlainObject(value)) {
if (!(key in defaults)) {
Object.assign(defaults, { [key]: {} });
}
Options(defaults[key], value);
} else {
Object.assign(defaults, { [key]: value });
}
});
}
return Options(defaults, ...options);
};

View File

@ -1,18 +0,0 @@
/** unnecessary */
import type { Shard } from '../services/shard';
export async function checkOffline(
shard: Shard,
highPriority: boolean
): Promise<void> {
if (!shard.isOpen()) {
await new Promise(resolve => {
if (highPriority) {
shard.offlineSendQueue.unshift(resolve);
} else {
shard.offlineSendQueue.push(resolve);
}
});
}
}