@ovencord/util 1.1.2 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/AsyncEventEmitter.ts +160 -26
- package/src/Snowflake.ts +201 -0
- package/src/functions/userAgentAppendix.ts +11 -13
- package/src/index.ts +2 -0
package/package.json
CHANGED
package/src/AsyncEventEmitter.ts
CHANGED
|
@@ -1,28 +1,162 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
export type EventMap = Record<string | symbol, any[]>;
|
|
2
|
+
|
|
3
|
+
export class AsyncEventEmitter<Events extends EventMap = EventMap> {
|
|
4
|
+
private _listeners = new Map<keyof Events | string | symbol, Set<Function>>();
|
|
5
|
+
private _maxListeners = 10;
|
|
6
|
+
|
|
7
|
+
public on<K extends keyof Events>(event: K, listener: (...args: Events[K]) => void | Promise<void>): this;
|
|
8
|
+
public on<K extends string | symbol>(event: K, listener: (...args: any[]) => void | Promise<void>): this;
|
|
9
|
+
public on(event: string | symbol, listener: Function): this {
|
|
10
|
+
if (!this._listeners.has(event)) {
|
|
11
|
+
this._listeners.set(event, new Set());
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const listeners = this._listeners.get(event)!;
|
|
15
|
+
listeners.add(listener);
|
|
16
|
+
|
|
17
|
+
if (listeners.size > this._maxListeners) {
|
|
18
|
+
console.warn(
|
|
19
|
+
`Possible AsyncEventEmitter memory leak detected. ` +
|
|
20
|
+
`${listeners.size} ${String(event)} listeners added. ` +
|
|
21
|
+
`Use emitter.setMaxListeners() to increase limit`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public addListener<K extends keyof Events>(event: K, listener: (...args: Events[K]) => void | Promise<void>): this;
|
|
29
|
+
public addListener<K extends string | symbol>(event: K, listener: (...args: any[]) => void | Promise<void>): this;
|
|
30
|
+
public addListener(event: string | symbol, listener: Function): this {
|
|
31
|
+
return this.on(event, listener as any);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public once<K extends keyof Events>(event: K, listener: (...args: Events[K]) => void | Promise<void>): this;
|
|
35
|
+
public once<K extends string | symbol>(event: K, listener: (...args: any[]) => void | Promise<void>): this;
|
|
36
|
+
public once(event: string | symbol, listener: Function): this {
|
|
37
|
+
const wrapper = async (...args: any[]) => {
|
|
38
|
+
this.off(event, wrapper);
|
|
39
|
+
await listener(...args);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
Object.defineProperty(wrapper, '_originalListener', {
|
|
43
|
+
value: listener,
|
|
44
|
+
writable: false,
|
|
45
|
+
enumerable: false,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return this.on(event, wrapper);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public off<K extends keyof Events>(event: K, listener: (...args: Events[K]) => void | Promise<void>): this;
|
|
52
|
+
public off<K extends string | symbol>(event: K, listener: (...args: any[]) => void | Promise<void>): this;
|
|
53
|
+
public off(event: string | symbol, listener: Function): this {
|
|
54
|
+
const listeners = this._listeners.get(event);
|
|
55
|
+
if (!listeners) return this;
|
|
56
|
+
|
|
57
|
+
for (const fn of listeners) {
|
|
58
|
+
if (fn === listener || (fn as any)._originalListener === listener) {
|
|
59
|
+
listeners.delete(fn);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (listeners.size === 0) {
|
|
64
|
+
this._listeners.delete(event);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public removeListener<K extends keyof Events>(event: K, listener: (...args: Events[K]) => void | Promise<void>): this;
|
|
71
|
+
public removeListener<K extends string | symbol>(event: K, listener: (...args: any[]) => void | Promise<void>): this;
|
|
72
|
+
public removeListener(event: string | symbol, listener: Function): this {
|
|
73
|
+
return this.off(event, listener as any);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public async emit<K extends keyof Events>(event: K, ...args: Events[K]): Promise<boolean>;
|
|
77
|
+
public async emit<K extends string | symbol>(event: K, ...args: any[]): Promise<boolean>;
|
|
78
|
+
public async emit(event: string | symbol, ...args: any[]): Promise<boolean> {
|
|
79
|
+
const listeners = this._listeners.get(event);
|
|
80
|
+
if (!listeners?.size) return false;
|
|
81
|
+
|
|
82
|
+
// We copy the listeners to ensure that if a listener is removed during execution,
|
|
83
|
+
// the loop still iterates over the snapshot of listeners at the time of emission.
|
|
84
|
+
// However, for strict sequential execution where one might remove another,
|
|
85
|
+
// iterating over the live Set or a copy is a design choice.
|
|
86
|
+
// Discord.js usually handles this by copying or iterating safe.
|
|
87
|
+
// The user's snippet iterates directly over the listeners collection from .get().
|
|
88
|
+
// If we use 'for of' on a Set, it handles deletions gracefully (the deleted item won't be visited if not reached yet),
|
|
89
|
+
// but additions might be visited.
|
|
90
|
+
// For now, adhering to user's "for (const listener of listeners)" pattern.
|
|
91
|
+
|
|
92
|
+
// NOTE: Iterating over the Set directly allows listeners to remove themselves safely.
|
|
93
|
+
for (const listener of listeners) {
|
|
94
|
+
try {
|
|
95
|
+
await listener(...args);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (event === 'error') {
|
|
98
|
+
console.error('Error in error handler:', error);
|
|
99
|
+
} else if (this.listenerCount('error') > 0) {
|
|
100
|
+
await this.emit('error', error);
|
|
101
|
+
} else {
|
|
102
|
+
console.error(`Unhandled error in ${String(event)} event:`, error);
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
27
106
|
}
|
|
107
|
+
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public removeAllListeners(event?: keyof Events | string | symbol): this {
|
|
112
|
+
if (event !== undefined) {
|
|
113
|
+
this._listeners.delete(event);
|
|
114
|
+
} else {
|
|
115
|
+
this._listeners.clear();
|
|
116
|
+
}
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public setMaxListeners(n: number): this {
|
|
121
|
+
this._maxListeners = n;
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public getMaxListeners(): number {
|
|
126
|
+
return this._maxListeners;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public listenerCount(event: keyof Events | string | symbol): number {
|
|
130
|
+
return this._listeners.get(event)?.size ?? 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public eventNames(): Array<keyof Events | string | symbol> {
|
|
134
|
+
return Array.from(this._listeners.keys());
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public rawListeners(event: keyof Events | string | symbol): Function[] {
|
|
138
|
+
return Array.from(this._listeners.get(event) ?? []);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public waitFor<K extends keyof Events>(event: K, timeout?: number): Promise<Events[K]>;
|
|
142
|
+
public waitFor<K extends string | symbol>(event: K, timeout?: number): Promise<any[]>;
|
|
143
|
+
public waitFor(event: string | symbol, timeout?: number): Promise<any[]> {
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
let timeoutId: Timer | undefined;
|
|
146
|
+
|
|
147
|
+
const listener = (...args: any[]) => {
|
|
148
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
149
|
+
resolve(args);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
this.once(event, listener);
|
|
153
|
+
|
|
154
|
+
if (timeout) {
|
|
155
|
+
timeoutId = setTimeout(() => {
|
|
156
|
+
this.off(event, listener);
|
|
157
|
+
reject(new Error(`Timeout waiting for ${String(event)}`));
|
|
158
|
+
}, timeout);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
28
162
|
}
|
package/src/Snowflake.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for {@link Snowflake.generate}
|
|
3
|
+
*/
|
|
4
|
+
export interface SnowflakeGenerateOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Timestamp or date of the snowflake to generate
|
|
7
|
+
* @default Date.now()
|
|
8
|
+
*/
|
|
9
|
+
timestamp?: number | bigint | Date;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The increment to use
|
|
13
|
+
* @default 0n
|
|
14
|
+
* @remark keep in mind that this bigint is auto-incremented between generate calls
|
|
15
|
+
*/
|
|
16
|
+
increment?: bigint;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The worker ID to use, will be truncated to 5 bits (0-31)
|
|
20
|
+
* @default 0n
|
|
21
|
+
*/
|
|
22
|
+
workerId?: bigint;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The process ID to use, will be truncated to 5 bits (0-31)
|
|
26
|
+
* @default 1n
|
|
27
|
+
*/
|
|
28
|
+
processId?: bigint;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Object returned by {@link Snowflake.deconstruct}
|
|
33
|
+
*/
|
|
34
|
+
export interface DeconstructedSnowflake {
|
|
35
|
+
/** The id in BigInt form */
|
|
36
|
+
id: bigint;
|
|
37
|
+
/** The timestamp stored in the snowflake */
|
|
38
|
+
timestamp: bigint;
|
|
39
|
+
/** The worker id stored in the snowflake */
|
|
40
|
+
workerId: bigint;
|
|
41
|
+
/** The process id stored in the snowflake */
|
|
42
|
+
processId: bigint;
|
|
43
|
+
/** The increment stored in the snowflake */
|
|
44
|
+
increment: bigint;
|
|
45
|
+
/** The epoch to use in the snowflake */
|
|
46
|
+
epoch: bigint;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const MaximumWorkerId = 0b11111n;
|
|
50
|
+
const MaximumProcessId = 0b11111n;
|
|
51
|
+
const MaximumIncrement = 0b111111111111n;
|
|
52
|
+
const TimestampFieldDivisor = 2 ** 22;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A class for generating and deconstructing snowflakes.
|
|
56
|
+
*
|
|
57
|
+
* A snowflake is a 64-bit unsigned integer with 4 fields that have a fixed epoch value.
|
|
58
|
+
*
|
|
59
|
+
* ```
|
|
60
|
+
* 64 22 17 12 0
|
|
61
|
+
* 000000111011000111100001101001000101000000 00001 00000 000000000000
|
|
62
|
+
* number of ms since epoch worker pid increment
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export class Snowflake {
|
|
66
|
+
/** Alias for {@link deconstruct} */
|
|
67
|
+
public readonly decode = this.deconstruct.bind(this);
|
|
68
|
+
|
|
69
|
+
#epoch: bigint;
|
|
70
|
+
#epochNumber: number;
|
|
71
|
+
#increment = 0n;
|
|
72
|
+
#processId = 1n;
|
|
73
|
+
#workerId = 0n;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param epoch the epoch to use
|
|
77
|
+
*/
|
|
78
|
+
constructor(epoch: number | bigint | Date) {
|
|
79
|
+
this.#epoch = BigInt(epoch instanceof Date ? epoch.getTime() : epoch);
|
|
80
|
+
this.#epochNumber = Number(this.#epoch);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The epoch for this snowflake, as a bigint */
|
|
84
|
+
get epoch(): bigint {
|
|
85
|
+
return this.#epoch;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The epoch for this snowflake, as a number */
|
|
89
|
+
get epochNumber(): number {
|
|
90
|
+
return this.#epochNumber;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Gets the configured process ID */
|
|
94
|
+
get processId(): bigint {
|
|
95
|
+
return this.#processId;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Sets the process ID (masked with `0b11111n`) */
|
|
99
|
+
set processId(value: number | bigint) {
|
|
100
|
+
this.#processId = BigInt(value) & MaximumProcessId;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Gets the configured worker ID */
|
|
104
|
+
get workerId(): bigint {
|
|
105
|
+
return this.#workerId;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Sets the worker ID (masked with `0b11111n`) */
|
|
109
|
+
set workerId(value: number | bigint) {
|
|
110
|
+
this.#workerId = BigInt(value) & MaximumWorkerId;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Generates a snowflake given an epoch and optionally a timestamp.
|
|
115
|
+
*
|
|
116
|
+
* @param options - Options to pass into the generator
|
|
117
|
+
* @returns A unique snowflake
|
|
118
|
+
*/
|
|
119
|
+
generate({
|
|
120
|
+
increment,
|
|
121
|
+
timestamp = Date.now(),
|
|
122
|
+
workerId = this.#workerId,
|
|
123
|
+
processId = this.#processId,
|
|
124
|
+
}: SnowflakeGenerateOptions = {}): bigint {
|
|
125
|
+
if (timestamp instanceof Date) timestamp = BigInt(timestamp.getTime());
|
|
126
|
+
else if (typeof timestamp === 'number') timestamp = BigInt(timestamp);
|
|
127
|
+
else if (typeof timestamp !== 'bigint') {
|
|
128
|
+
throw new TypeError(`"timestamp" argument must be a number, bigint, or Date (received ${typeof timestamp})`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (typeof increment !== 'bigint') {
|
|
132
|
+
increment = this.#increment;
|
|
133
|
+
this.#increment = (increment + 1n) & MaximumIncrement;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
((timestamp - this.#epoch) << 22n) |
|
|
138
|
+
((workerId & MaximumWorkerId) << 17n) |
|
|
139
|
+
((processId & MaximumProcessId) << 12n) |
|
|
140
|
+
(increment & MaximumIncrement)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Deconstructs a snowflake given a snowflake ID.
|
|
146
|
+
*
|
|
147
|
+
* @param id - The snowflake to deconstruct
|
|
148
|
+
* @returns A deconstructed snowflake
|
|
149
|
+
*/
|
|
150
|
+
deconstruct(id: string | bigint): DeconstructedSnowflake {
|
|
151
|
+
const bigIntId = BigInt(id);
|
|
152
|
+
const epoch = this.#epoch;
|
|
153
|
+
return {
|
|
154
|
+
id: bigIntId,
|
|
155
|
+
timestamp: (bigIntId >> 22n) + epoch,
|
|
156
|
+
workerId: (bigIntId >> 17n) & MaximumWorkerId,
|
|
157
|
+
processId: (bigIntId >> 12n) & MaximumProcessId,
|
|
158
|
+
increment: bigIntId & MaximumIncrement,
|
|
159
|
+
epoch,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Retrieves the timestamp field's value from a snowflake.
|
|
165
|
+
*
|
|
166
|
+
* @param id - The snowflake to get the timestamp value from
|
|
167
|
+
* @returns The UNIX timestamp that is stored in `id`
|
|
168
|
+
*/
|
|
169
|
+
timestampFrom(id: string | bigint): number {
|
|
170
|
+
return Math.floor(Number(id) / TimestampFieldDivisor) + this.#epochNumber;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Compares two snowflakes.
|
|
175
|
+
*
|
|
176
|
+
* @param a - The first snowflake to compare
|
|
177
|
+
* @param b - The second snowflake to compare
|
|
178
|
+
* @returns `-1` if `a` is older than `b`, `0` if equal, `1` if `a` is newer than `b`
|
|
179
|
+
*/
|
|
180
|
+
static compare(a: string | bigint, b: string | bigint): -1 | 0 | 1 {
|
|
181
|
+
const typeA = typeof a;
|
|
182
|
+
if (typeA === typeof b) {
|
|
183
|
+
if (typeA === 'string') {
|
|
184
|
+
return a === b ? 0 : (a as string).length < (b as string).length ? -1 : (a as string).length > (b as string).length ? 1 : a < b ? -1 : 1;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return a === b ? 0 : (a as bigint) < (b as bigint) ? -1 : 1;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const bigA = BigInt(a);
|
|
191
|
+
const bigB = BigInt(b);
|
|
192
|
+
return bigA === bigB ? 0 : bigA < bigB ? -1 : 1;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* A {@link Snowflake} instance using Discord's epoch (2015-01-01T00:00:00.000Z).
|
|
198
|
+
*
|
|
199
|
+
* @see {@link https://discord.com/developers/docs/reference#snowflakes}
|
|
200
|
+
*/
|
|
201
|
+
export const DiscordSnowflake = new Snowflake(1420070400000n);
|
|
@@ -5,29 +5,27 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export function getUserAgentAppendix(): string {
|
|
7
7
|
// https://vercel.com/docs/concepts/functions/edge-functions/edge-runtime#check-if-you're-running-on-the-edge-runtime
|
|
8
|
-
|
|
9
|
-
if (typeof globalThis.EdgeRuntime !== 'undefined') {
|
|
8
|
+
if (typeof (globalThis as any).EdgeRuntime !== 'undefined') {
|
|
10
9
|
return 'Vercel-Edge-Functions';
|
|
11
10
|
}
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
if (typeof globalThis.R2 !== 'undefined' && typeof globalThis.WebSocketPair !== 'undefined') {
|
|
11
|
+
|
|
12
|
+
// Cloudflare Workers
|
|
13
|
+
if (typeof (globalThis as any).R2 !== 'undefined' && typeof (globalThis as any).WebSocketPair !== 'undefined') {
|
|
15
14
|
// https://developers.cloudflare.com/workers/runtime-apis/web-standards/#navigatoruseragent
|
|
16
15
|
return 'Cloudflare-Workers';
|
|
17
16
|
}
|
|
18
|
-
|
|
17
|
+
|
|
19
18
|
// https://docs.netlify.com/edge-functions/api/#netlify-global-object
|
|
20
|
-
|
|
21
|
-
if (typeof globalThis.Netlify !== 'undefined') {
|
|
19
|
+
if (typeof (globalThis as any).Netlify !== 'undefined') {
|
|
22
20
|
return 'Netlify-Edge-Functions';
|
|
23
21
|
}
|
|
24
|
-
|
|
22
|
+
|
|
25
23
|
// Most (if not all) edge environments will have `process` defined. Within a web browser we'll extract it using `navigator.userAgent`.
|
|
26
|
-
if (typeof globalThis.process !== 'object') {
|
|
27
|
-
if (typeof globalThis.navigator === 'object') {
|
|
28
|
-
return globalThis.navigator.userAgent;
|
|
24
|
+
if (typeof (globalThis as any).process !== 'object') {
|
|
25
|
+
if (typeof (globalThis as any).navigator === 'object') {
|
|
26
|
+
return (globalThis as any).navigator.userAgent;
|
|
29
27
|
}
|
|
30
|
-
|
|
28
|
+
|
|
31
29
|
return 'UnknownEnvironment';
|
|
32
30
|
}
|
|
33
31
|
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export type * from './types.js';
|
|
2
|
+
export { DiscordSnowflake, Snowflake as SnowflakeClass } from './Snowflake.js';
|
|
3
|
+
export type { DeconstructedSnowflake, SnowflakeGenerateOptions } from './Snowflake.js';
|
|
2
4
|
export * from './functions/index.js';
|
|
3
5
|
export * from './encodables.js';
|
|
4
6
|
export type * from './RawFile.js';
|