@glade-chat/glade.js 0.1.0
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/LICENSE +21 -0
- package/README.md +52 -0
- package/package.json +57 -0
- package/src/client/Client.js +355 -0
- package/src/errors/index.js +59 -0
- package/src/gateway/Gateway.js +172 -0
- package/src/gateway/handleDispatch.js +259 -0
- package/src/index.js +66 -0
- package/src/managers/CachedManager.js +95 -0
- package/src/managers/ChannelManager.js +40 -0
- package/src/managers/DMManager.js +43 -0
- package/src/managers/FriendManager.js +89 -0
- package/src/managers/HouseManager.js +44 -0
- package/src/managers/InviteManager.js +46 -0
- package/src/managers/MemberManager.js +41 -0
- package/src/managers/MessageManager.js +69 -0
- package/src/managers/RoleManager.js +65 -0
- package/src/managers/RoomManager.js +80 -0
- package/src/managers/UserManager.js +41 -0
- package/src/rest/REST.js +250 -0
- package/src/rest/Routes.js +85 -0
- package/src/structures/Base.js +70 -0
- package/src/structures/ClientUser.js +142 -0
- package/src/structures/DMChannel.js +85 -0
- package/src/structures/House.js +171 -0
- package/src/structures/Invite.js +83 -0
- package/src/structures/Member.js +148 -0
- package/src/structures/Message.js +176 -0
- package/src/structures/ReactionGroup.js +42 -0
- package/src/structures/Role.js +107 -0
- package/src/structures/Room.js +264 -0
- package/src/structures/User.js +113 -0
- package/src/structures/VoiceState.js +42 -0
- package/src/util/Collection.js +167 -0
- package/src/util/Constants.js +183 -0
- package/src/util/Permissions.js +148 -0
- package/src/util/Util.js +62 -0
- package/types/index.d.ts +784 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { io } from 'socket.io-client';
|
|
4
|
+
import { GladeGatewayError } from '../errors/index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The realtime layer of glade.js: a thin wrapper around a Socket.IO client
|
|
8
|
+
* connection to the Glade gateway. It authenticates the handshake with the
|
|
9
|
+
* client's access token, transparently refreshes + reconnects when the token
|
|
10
|
+
* expires, and forwards every server dispatch to a single handler.
|
|
11
|
+
*/
|
|
12
|
+
export class Gateway {
|
|
13
|
+
/**
|
|
14
|
+
* @param {object} opts
|
|
15
|
+
* @param {string} opts.url Gateway origin.
|
|
16
|
+
* @param {() => (string | null)} opts.getToken Returns the current access token.
|
|
17
|
+
* @param {() => Promise<string | null>} opts.refresh Refreshes the token; returns the new one.
|
|
18
|
+
* @param {(event: string, data: any) => void} opts.onDispatch Receives every server event.
|
|
19
|
+
* @param {() => void} [opts.onConnect]
|
|
20
|
+
* @param {(reason: string) => void} [opts.onDisconnect]
|
|
21
|
+
* @param {(err: Error) => void} [opts.onError]
|
|
22
|
+
* @param {(msg: string) => void} [opts.debug]
|
|
23
|
+
* @param {object} [opts.ws] Extra socket.io-client options.
|
|
24
|
+
*/
|
|
25
|
+
constructor(opts) {
|
|
26
|
+
this.url = opts.url;
|
|
27
|
+
this.getToken = opts.getToken;
|
|
28
|
+
this.refresh = opts.refresh;
|
|
29
|
+
this.onDispatch = opts.onDispatch;
|
|
30
|
+
this.onConnect = opts.onConnect ?? (() => {});
|
|
31
|
+
this.onDisconnect = opts.onDisconnect ?? (() => {});
|
|
32
|
+
this.onError = opts.onError ?? (() => {});
|
|
33
|
+
this._debug = opts.debug ?? null;
|
|
34
|
+
this.wsOptions = opts.ws ?? {};
|
|
35
|
+
|
|
36
|
+
/** @type {import('socket.io-client').Socket | null} */
|
|
37
|
+
this.socket = null;
|
|
38
|
+
/** @type {string | null} The socket id assigned on connect. */
|
|
39
|
+
this.id = null;
|
|
40
|
+
this._refreshingHandshake = false;
|
|
41
|
+
/** Consecutive handshake-refresh attempts, reset on a successful connect. */
|
|
42
|
+
this._handshakeRefreshAttempts = 0;
|
|
43
|
+
/** Cap on consecutive handshake refreshes to avoid a reconnect loop. */
|
|
44
|
+
this._maxHandshakeRefresh = opts.maxHandshakeRefresh ?? 5;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Whether the underlying socket is currently connected. */
|
|
48
|
+
get connected() {
|
|
49
|
+
return Boolean(this.socket?.connected);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Opens the gateway connection. Safe to call once; subsequent calls reconnect
|
|
54
|
+
* the existing socket.
|
|
55
|
+
*/
|
|
56
|
+
connect() {
|
|
57
|
+
if (this.socket) {
|
|
58
|
+
if (!this.socket.connected) this.socket.connect();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.#debug(`Connecting to ${this.url}`);
|
|
63
|
+
this.socket = io(this.url, {
|
|
64
|
+
autoConnect: false,
|
|
65
|
+
withCredentials: true,
|
|
66
|
+
auth: (cb) => cb({ token: this.getToken() }),
|
|
67
|
+
...this.wsOptions,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
this.socket.on('connect', () => {
|
|
71
|
+
this.id = this.socket.id;
|
|
72
|
+
this._handshakeRefreshAttempts = 0;
|
|
73
|
+
this.#debug(`Connected (socket ${this.id})`);
|
|
74
|
+
this.onConnect();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
this.socket.on('disconnect', (reason) => {
|
|
78
|
+
this.#debug(`Disconnected: ${reason}`);
|
|
79
|
+
this.onDisconnect(reason);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// If the handshake is rejected for an expired/invalid token, refresh once and
|
|
83
|
+
// reconnect; the auth callback re-reads the token on the next attempt.
|
|
84
|
+
this.socket.on('connect_error', async (err) => {
|
|
85
|
+
this.#debug(`connect_error: ${err.message}`);
|
|
86
|
+
const tokenError = err.message?.toLowerCase().includes('token');
|
|
87
|
+
const canRefresh =
|
|
88
|
+
tokenError &&
|
|
89
|
+
!this._refreshingHandshake &&
|
|
90
|
+
this._handshakeRefreshAttempts < this._maxHandshakeRefresh;
|
|
91
|
+
if (canRefresh) {
|
|
92
|
+
this._refreshingHandshake = true;
|
|
93
|
+
this._handshakeRefreshAttempts += 1;
|
|
94
|
+
try {
|
|
95
|
+
const fresh = await this.refresh();
|
|
96
|
+
if (fresh) this.socket.connect();
|
|
97
|
+
else this.onError(new GladeGatewayError(`Gateway auth failed: ${err.message}`));
|
|
98
|
+
} finally {
|
|
99
|
+
this._refreshingHandshake = false;
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
this.onError(new GladeGatewayError(err.message));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Forward every server-sent event to the dispatch handler.
|
|
107
|
+
this.socket.onAny((event, ...args) => {
|
|
108
|
+
this.onDispatch(event, args.length <= 1 ? args[0] : args);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
this.socket.connect();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Closes the gateway connection. */
|
|
115
|
+
disconnect() {
|
|
116
|
+
if (this.socket) {
|
|
117
|
+
this.socket.disconnect();
|
|
118
|
+
this.socket = null;
|
|
119
|
+
this.id = null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Emits an event to the server without waiting for an acknowledgement.
|
|
125
|
+
* @param {string} event
|
|
126
|
+
* @param {...any} args
|
|
127
|
+
*/
|
|
128
|
+
send(event, ...args) {
|
|
129
|
+
if (!this.socket) throw new GladeGatewayError('Gateway is not connected');
|
|
130
|
+
this.socket.emit(event, ...args);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Emits an event and resolves with the server's acknowledgement. Rejects with a
|
|
135
|
+
* {@link GladeGatewayError} when the ack reports `ok: false` (e.g. rate limited).
|
|
136
|
+
* @param {string} event
|
|
137
|
+
* @param {any} [payload]
|
|
138
|
+
* @param {number} [timeout=15000] ms before rejecting.
|
|
139
|
+
* @returns {Promise<any>}
|
|
140
|
+
*/
|
|
141
|
+
request(event, payload, timeout = 15_000) {
|
|
142
|
+
return new Promise((resolve, reject) => {
|
|
143
|
+
if (!this.socket) return reject(new GladeGatewayError('Gateway is not connected'));
|
|
144
|
+
let settled = false;
|
|
145
|
+
const timer = setTimeout(() => {
|
|
146
|
+
if (settled) return;
|
|
147
|
+
settled = true;
|
|
148
|
+
reject(new GladeGatewayError(`Gateway request "${event}" timed out`));
|
|
149
|
+
}, timeout);
|
|
150
|
+
|
|
151
|
+
const ack = (response) => {
|
|
152
|
+
if (settled) return;
|
|
153
|
+
settled = true;
|
|
154
|
+
clearTimeout(timer);
|
|
155
|
+
if (response && response.ok === false) {
|
|
156
|
+
reject(new GladeGatewayError(response.error || `"${event}" failed`, response.code));
|
|
157
|
+
} else {
|
|
158
|
+
resolve(response);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
if (payload === undefined) this.socket.emit(event, ack);
|
|
163
|
+
else this.socket.emit(event, payload, ack);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#debug(msg) {
|
|
168
|
+
this._debug?.(`[Gateway] ${msg}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export default Gateway;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { Events, GatewayDispatch } from '../util/Constants.js';
|
|
4
|
+
import { VoiceState } from '../structures/VoiceState.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Translates a raw gateway event into cache mutations and client-facing events.
|
|
8
|
+
* Invoked for every server dispatch by the {@link Gateway}.
|
|
9
|
+
*
|
|
10
|
+
* @param {import('../client/Client.js').Client} client
|
|
11
|
+
* @param {string} event Raw Socket.IO event name.
|
|
12
|
+
* @param {any} data Event payload.
|
|
13
|
+
*/
|
|
14
|
+
export function handleDispatch(client, event, data) {
|
|
15
|
+
// Surface every raw event for power users / debugging.
|
|
16
|
+
client.emit(Events.Raw, event, data);
|
|
17
|
+
|
|
18
|
+
switch (event) {
|
|
19
|
+
case GatewayDispatch.Ready: {
|
|
20
|
+
client.ready = true;
|
|
21
|
+
// The server only auto-joins user/house/DM channels — explicitly subscribe
|
|
22
|
+
// to each cached room so we receive room messages, typing, and reactions.
|
|
23
|
+
client._subscribeCachedRooms();
|
|
24
|
+
client.emit(Events.Ready, client);
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
case GatewayDispatch.MessageNew: {
|
|
29
|
+
const message = client._cacheMessage(data);
|
|
30
|
+
client.emit(Events.MessageCreate, message);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
case GatewayDispatch.MessageUpdated: {
|
|
35
|
+
const existing = findCachedMessage(client, data);
|
|
36
|
+
const old = existing ? existing._clone() : null;
|
|
37
|
+
const message = client._cacheMessage(data);
|
|
38
|
+
client.emit(Events.MessageUpdate, old, message);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
case GatewayDispatch.MessageDeleted: {
|
|
43
|
+
const existing = findCachedMessage(client, data);
|
|
44
|
+
removeCachedMessage(client, data);
|
|
45
|
+
client.emit(Events.MessageDelete, existing ?? data);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
case GatewayDispatch.MessagePinned: {
|
|
50
|
+
const existing = findCachedMessage(client, { id: data.messageId, roomId: data.roomId });
|
|
51
|
+
if (existing) existing.pinned = data.pinned;
|
|
52
|
+
client.emit(Events.MessagePinUpdate, {
|
|
53
|
+
roomId: data.roomId,
|
|
54
|
+
messageId: data.messageId,
|
|
55
|
+
pinned: data.pinned,
|
|
56
|
+
message: existing ?? null,
|
|
57
|
+
});
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
case GatewayDispatch.ReactionUpdated: {
|
|
62
|
+
const existing = findCachedMessage(client, {
|
|
63
|
+
id: data.messageId,
|
|
64
|
+
roomId: data.roomId,
|
|
65
|
+
dmChannelId: data.dmChannelId,
|
|
66
|
+
});
|
|
67
|
+
if (existing) existing._patch({ reactions: data.reactions });
|
|
68
|
+
client.emit(Events.MessageReactionUpdate, {
|
|
69
|
+
messageId: data.messageId,
|
|
70
|
+
roomId: data.roomId ?? null,
|
|
71
|
+
dmChannelId: data.dmChannelId ?? null,
|
|
72
|
+
reactions: existing ? existing.reactions : data.reactions,
|
|
73
|
+
message: existing ?? null,
|
|
74
|
+
});
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case GatewayDispatch.Mention: {
|
|
79
|
+
const message = client._cacheMessage(data.message);
|
|
80
|
+
client.emit(Events.Mention, {
|
|
81
|
+
houseId: data.houseId,
|
|
82
|
+
roomId: data.roomId,
|
|
83
|
+
message,
|
|
84
|
+
});
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
case GatewayDispatch.DmIncoming: {
|
|
89
|
+
// The message also arrives via `message:new` (the socket is in the DM room),
|
|
90
|
+
// so we only ensure the channel/message are cached here — no duplicate event.
|
|
91
|
+
client._cacheMessage(data.message);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
case GatewayDispatch.Typing: {
|
|
96
|
+
const user = client.users.cache.get(data.userId) ?? null;
|
|
97
|
+
client.emit(Events.TypingStart, {
|
|
98
|
+
userId: data.userId,
|
|
99
|
+
handle: data.handle,
|
|
100
|
+
user,
|
|
101
|
+
roomId: data.roomId ?? null,
|
|
102
|
+
dmChannelId: data.dmChannelId ?? null,
|
|
103
|
+
typing: data.typing,
|
|
104
|
+
});
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
case GatewayDispatch.PresenceUpdate: {
|
|
109
|
+
const user = client.users.cache.get(data.userId);
|
|
110
|
+
if (user) user.status = data.status;
|
|
111
|
+
// Reflect on cached House members too.
|
|
112
|
+
for (const house of client.houses.cache.values()) {
|
|
113
|
+
const member = house.members.cache.get(data.userId);
|
|
114
|
+
if (member) member.status = data.status;
|
|
115
|
+
}
|
|
116
|
+
client.emit(Events.PresenceUpdate, { userId: data.userId, status: data.status, user: user ?? null });
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
case GatewayDispatch.RoomCreated: {
|
|
121
|
+
const house = client.houses.cache.get(data.houseId);
|
|
122
|
+
const room = house ? house.rooms._add(data) : null;
|
|
123
|
+
client.emit(Events.RoomCreate, room ?? data);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
case GatewayDispatch.RoomUpdated: {
|
|
128
|
+
if (data.id) {
|
|
129
|
+
const existing = client.channels.cache.get(data.id);
|
|
130
|
+
const old = existing ? existing._clone() : null;
|
|
131
|
+
const house = client.houses.cache.get(data.houseId ?? existing?.houseId);
|
|
132
|
+
const room = house ? house.rooms._add(data) : existing;
|
|
133
|
+
client.emit(Events.RoomUpdate, old, room ?? data);
|
|
134
|
+
} else {
|
|
135
|
+
// Override-only update: just a houseId hint to refetch.
|
|
136
|
+
client.emit(Events.RoomUpdate, null, data);
|
|
137
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
case GatewayDispatch.RoomDeleted: {
|
|
142
|
+
const existing = client.channels.cache.get(data.roomId) ?? null;
|
|
143
|
+
const house = client.houses.cache.get(data.houseId);
|
|
144
|
+
house?.rooms.cache.delete(data.roomId);
|
|
145
|
+
client.channels.cache.delete(data.roomId);
|
|
146
|
+
client.emit(Events.RoomDelete, existing ?? data);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
case GatewayDispatch.RoomReordered: {
|
|
151
|
+
client.emit(Events.RoomsReorder, { houseId: data.houseId });
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
case GatewayDispatch.HouseUpdated: {
|
|
156
|
+
client.emit(Events.HouseUpdate, { houseId: data.houseId, house: client.houses.cache.get(data.houseId) ?? null });
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
case GatewayDispatch.HouseDeleted: {
|
|
161
|
+
const house = client.houses.cache.get(data.houseId) ?? null;
|
|
162
|
+
client.houses.cache.delete(data.houseId);
|
|
163
|
+
client.emit(Events.HouseDelete, house ?? data);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
case GatewayDispatch.RolesUpdated: {
|
|
168
|
+
client.emit(Events.RolesUpdate, { houseId: data.houseId });
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
case GatewayDispatch.MembersUpdated: {
|
|
173
|
+
client.emit(Events.MembersUpdate, { houseId: data.houseId });
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
case GatewayDispatch.MemberJoined: {
|
|
178
|
+
client.emit(Events.MemberJoin, { houseId: data.houseId, userId: data.userId });
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
case GatewayDispatch.FriendRequest: {
|
|
183
|
+
const user = data.user ? client.users._add(data.user) : null;
|
|
184
|
+
client.emit(Events.FriendRequest, { id: data.id, user });
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
case GatewayDispatch.FriendAccepted: {
|
|
189
|
+
const user = data.user ? client.users._add(data.user) : null;
|
|
190
|
+
if (user) client.friends.cache.set(user.id, user);
|
|
191
|
+
client.emit(Events.FriendAccepted, { user });
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
case GatewayDispatch.FriendRemoved: {
|
|
196
|
+
client.friends.cache.delete(data.userId);
|
|
197
|
+
client.emit(Events.FriendRemoved, { userId: data.userId });
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
case GatewayDispatch.SessionRevoked: {
|
|
202
|
+
client.emit(Events.SessionRevoked, { sessionId: data.sessionId });
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
case GatewayDispatch.VoicePeerJoined: {
|
|
207
|
+
client.emit(Events.VoicePeerJoin, new VoiceState(client, data));
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
case GatewayDispatch.VoicePeerLeft: {
|
|
212
|
+
client.emit(Events.VoicePeerLeave, { socketId: data.socketId });
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
case GatewayDispatch.VoicePeerState: {
|
|
217
|
+
client.emit(Events.VoicePeerUpdate, new VoiceState(client, data));
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
case GatewayDispatch.VoiceRoomState: {
|
|
222
|
+
client.emit(Events.VoiceRoomState, {
|
|
223
|
+
roomId: data.roomId,
|
|
224
|
+
users: (data.users ?? []).map((u) => new VoiceState(client, { ...u, roomId: data.roomId })),
|
|
225
|
+
});
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
case GatewayDispatch.VoiceSignal: {
|
|
230
|
+
client.emit(Events.VoiceSignal, data);
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
default:
|
|
235
|
+
// Unknown event — already surfaced via the `raw` event above.
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Finds a cached message in its owning channel, if both are cached. */
|
|
241
|
+
function findCachedMessage(client, ref) {
|
|
242
|
+
if (ref.roomId) {
|
|
243
|
+
const room = client.channels.cache.get(ref.roomId);
|
|
244
|
+
if (room) return room.messages.cache.get(ref.id) ?? null;
|
|
245
|
+
}
|
|
246
|
+
if (ref.dmChannelId) {
|
|
247
|
+
const dm = client.dms.cache.get(ref.dmChannelId);
|
|
248
|
+
if (dm) return dm.messages.cache.get(ref.id) ?? null;
|
|
249
|
+
}
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Removes a cached message from its owning channel. */
|
|
254
|
+
function removeCachedMessage(client, ref) {
|
|
255
|
+
if (ref.roomId) client.channels.cache.get(ref.roomId)?.messages.cache.delete(ref.id);
|
|
256
|
+
if (ref.dmChannelId) client.dms.cache.get(ref.dmChannelId)?.messages.cache.delete(ref.id);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export default handleDispatch;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* glade.js — a Node.js library for the Glade (glade.chat) API and realtime gateway.
|
|
5
|
+
* @module glade.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// --- Core ---
|
|
9
|
+
export { Client } from './client/Client.js';
|
|
10
|
+
export { REST } from './rest/REST.js';
|
|
11
|
+
export { Gateway } from './gateway/Gateway.js';
|
|
12
|
+
export { Routes } from './rest/Routes.js';
|
|
13
|
+
|
|
14
|
+
// --- Structures ---
|
|
15
|
+
export { Base } from './structures/Base.js';
|
|
16
|
+
export { User } from './structures/User.js';
|
|
17
|
+
export { ClientUser } from './structures/ClientUser.js';
|
|
18
|
+
export { House } from './structures/House.js';
|
|
19
|
+
export { Room, TextRoom, VoiceRoom, PortalRoom, createRoom } from './structures/Room.js';
|
|
20
|
+
export { Message } from './structures/Message.js';
|
|
21
|
+
export { Member } from './structures/Member.js';
|
|
22
|
+
export { Role } from './structures/Role.js';
|
|
23
|
+
export { DMChannel } from './structures/DMChannel.js';
|
|
24
|
+
export { Invite } from './structures/Invite.js';
|
|
25
|
+
export { ReactionGroup } from './structures/ReactionGroup.js';
|
|
26
|
+
export { VoiceState } from './structures/VoiceState.js';
|
|
27
|
+
|
|
28
|
+
// --- Managers ---
|
|
29
|
+
export { CachedManager } from './managers/CachedManager.js';
|
|
30
|
+
export { HouseManager } from './managers/HouseManager.js';
|
|
31
|
+
export { ChannelManager } from './managers/ChannelManager.js';
|
|
32
|
+
export { RoomManager } from './managers/RoomManager.js';
|
|
33
|
+
export { UserManager } from './managers/UserManager.js';
|
|
34
|
+
export { MessageManager } from './managers/MessageManager.js';
|
|
35
|
+
export { MemberManager } from './managers/MemberManager.js';
|
|
36
|
+
export { RoleManager } from './managers/RoleManager.js';
|
|
37
|
+
export { DMManager } from './managers/DMManager.js';
|
|
38
|
+
export { FriendManager } from './managers/FriendManager.js';
|
|
39
|
+
export { InviteManager } from './managers/InviteManager.js';
|
|
40
|
+
|
|
41
|
+
// --- Utilities ---
|
|
42
|
+
export { Collection } from './util/Collection.js';
|
|
43
|
+
export {
|
|
44
|
+
PermissionsBitField,
|
|
45
|
+
PermissionFlags,
|
|
46
|
+
ALL_PERMISSIONS,
|
|
47
|
+
DEFAULT_EVERYONE,
|
|
48
|
+
CHANNEL_OVERRIDABLE,
|
|
49
|
+
} from './util/Permissions.js';
|
|
50
|
+
export {
|
|
51
|
+
Events,
|
|
52
|
+
GatewayDispatch,
|
|
53
|
+
GatewayCommand,
|
|
54
|
+
RoomTypes,
|
|
55
|
+
PresenceStatus,
|
|
56
|
+
FriendStatus,
|
|
57
|
+
DefaultOptions,
|
|
58
|
+
REFRESH_COOKIE,
|
|
59
|
+
} from './util/Constants.js';
|
|
60
|
+
export * from './util/Util.js';
|
|
61
|
+
|
|
62
|
+
// --- Errors ---
|
|
63
|
+
export { GladeError, GladeAPIError, GladeGatewayError } from './errors/index.js';
|
|
64
|
+
|
|
65
|
+
/** The installed glade.js version. */
|
|
66
|
+
export const version = '0.1.0';
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { Collection } from '../util/Collection.js';
|
|
4
|
+
import { resolveId } from '../util/Util.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base class for all managers. Holds a {@link Collection} cache of structures and
|
|
8
|
+
* provides shared add/resolve helpers. Subclasses set {@link CachedManager#holds}
|
|
9
|
+
* and may override {@link CachedManager#_create} for structures needing extra
|
|
10
|
+
* constructor context.
|
|
11
|
+
* @abstract
|
|
12
|
+
*/
|
|
13
|
+
export class CachedManager {
|
|
14
|
+
/**
|
|
15
|
+
* @param {import('../client/Client.js').Client} client
|
|
16
|
+
* @param {Function} holds The structure class this manager caches.
|
|
17
|
+
*/
|
|
18
|
+
constructor(client, holds) {
|
|
19
|
+
/**
|
|
20
|
+
* @type {import('../client/Client.js').Client}
|
|
21
|
+
* @readonly
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(this, 'client', { value: client });
|
|
24
|
+
/**
|
|
25
|
+
* The structure class this manager holds.
|
|
26
|
+
* @type {Function}
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(this, 'holds', { value: holds });
|
|
29
|
+
/**
|
|
30
|
+
* The cache of structures, keyed by id.
|
|
31
|
+
* @type {Collection<string, any>}
|
|
32
|
+
*/
|
|
33
|
+
this.cache = new Collection();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Instantiates a new structure from raw data. Override when the structure needs
|
|
38
|
+
* more than `(client, data)`.
|
|
39
|
+
* @param {any} data
|
|
40
|
+
* @returns {any}
|
|
41
|
+
* @protected
|
|
42
|
+
*/
|
|
43
|
+
_create(data) {
|
|
44
|
+
return new this.holds(this.client, data);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Adds raw data to the cache, patching an existing entry if present.
|
|
49
|
+
* @param {any} data
|
|
50
|
+
* @param {boolean} [cache=true] Whether to store the result in the cache.
|
|
51
|
+
* @returns {any} The cached/created structure.
|
|
52
|
+
* @protected
|
|
53
|
+
*/
|
|
54
|
+
_add(data, cache = true) {
|
|
55
|
+
const id = data.id;
|
|
56
|
+
const existing = id != null ? this.cache.get(id) : undefined;
|
|
57
|
+
if (existing) {
|
|
58
|
+
existing._patch(data);
|
|
59
|
+
return existing;
|
|
60
|
+
}
|
|
61
|
+
const entry = this._create(data);
|
|
62
|
+
if (cache && this.client.options.cache && entry.id != null) {
|
|
63
|
+
this.cache.set(entry.id, entry);
|
|
64
|
+
}
|
|
65
|
+
return entry;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolves a value to a cached structure.
|
|
70
|
+
* @param {string | { id: string }} idOrInstance
|
|
71
|
+
* @returns {any | null}
|
|
72
|
+
*/
|
|
73
|
+
resolve(idOrInstance) {
|
|
74
|
+
if (idOrInstance instanceof this.holds) return idOrInstance;
|
|
75
|
+
if (typeof idOrInstance === 'string') return this.cache.get(idOrInstance) ?? null;
|
|
76
|
+
if (idOrInstance?.id) return this.cache.get(idOrInstance.id) ?? null;
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolves a value to an id.
|
|
82
|
+
* @param {string | { id: string }} idOrInstance
|
|
83
|
+
* @returns {string}
|
|
84
|
+
*/
|
|
85
|
+
resolveId(idOrInstance) {
|
|
86
|
+
return resolveId(idOrInstance);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Iterates the cache values. */
|
|
90
|
+
[Symbol.iterator]() {
|
|
91
|
+
return this.cache.values();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default CachedManager;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { CachedManager } from './CachedManager.js';
|
|
4
|
+
import { Room } from '../structures/Room.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The client-wide cache of rooms (channels) across every House the client is in.
|
|
8
|
+
* Rooms are added here by each House's {@link RoomManager}. There is no single-room
|
|
9
|
+
* REST endpoint, so {@link ChannelManager#fetch} resolves a room by fetching its
|
|
10
|
+
* House's rooms when necessary.
|
|
11
|
+
* @extends {CachedManager}
|
|
12
|
+
*/
|
|
13
|
+
export class ChannelManager extends CachedManager {
|
|
14
|
+
constructor(client) {
|
|
15
|
+
super(client, Room);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolves a room by id from the cache, fetching its House's rooms if needed.
|
|
20
|
+
* @param {string} id
|
|
21
|
+
* @param {{ force?: boolean }} [options]
|
|
22
|
+
* @returns {Promise<Room | null>}
|
|
23
|
+
*/
|
|
24
|
+
async fetch(id, { force = false } = {}) {
|
|
25
|
+
if (!force && this.cache.has(id)) return this.cache.get(id);
|
|
26
|
+
// Find which cached House owns it, then refresh that House's rooms.
|
|
27
|
+
const cached = this.cache.get(id);
|
|
28
|
+
const houseId = cached?.houseId;
|
|
29
|
+
if (houseId) {
|
|
30
|
+
const house = this.client.houses.cache.get(houseId);
|
|
31
|
+
if (house) {
|
|
32
|
+
await house.rooms.fetch();
|
|
33
|
+
return this.cache.get(id) ?? null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return this.cache.get(id) ?? null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default ChannelManager;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { CachedManager } from './CachedManager.js';
|
|
4
|
+
import { DMChannel } from '../structures/DMChannel.js';
|
|
5
|
+
import { Routes } from '../rest/Routes.js';
|
|
6
|
+
import { resolveId } from '../util/Util.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Manages the client's direct-message channels.
|
|
10
|
+
* @extends {CachedManager}
|
|
11
|
+
*/
|
|
12
|
+
export class DMManager extends CachedManager {
|
|
13
|
+
constructor(client) {
|
|
14
|
+
super(client, DMChannel);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Fetches all of the client's DM channels.
|
|
19
|
+
* @returns {Promise<import('../util/Collection.js').Collection<string, DMChannel>>}
|
|
20
|
+
*/
|
|
21
|
+
async fetch() {
|
|
22
|
+
const { dms } = await this.client.rest.get(Routes.dms());
|
|
23
|
+
for (const dm of dms) this._add(dm);
|
|
24
|
+
return this.cache;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Opens (or returns the existing) DM channel with a user.
|
|
29
|
+
* @param {string | import('../structures/User.js').User} user
|
|
30
|
+
* @returns {Promise<DMChannel>}
|
|
31
|
+
*/
|
|
32
|
+
async create(user) {
|
|
33
|
+
const userId = resolveId(user);
|
|
34
|
+
const { dm } = await this.client.rest.post(Routes.dms(), { userId });
|
|
35
|
+
const channel = this._add({ id: dm.id });
|
|
36
|
+
// The REST response only carries the channel id; remember the recipient.
|
|
37
|
+
if (!channel.participantIds.includes(userId)) channel.participantIds.push(userId);
|
|
38
|
+
if (typeof user === 'object') this.client.users._add(user);
|
|
39
|
+
return channel;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default DMManager;
|