@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,183 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default {@link Client} options. The backend URLs default to the public Glade
|
|
5
|
+
* service; override `rest`/`gateway` to target a self-hosted deployment.
|
|
6
|
+
*/
|
|
7
|
+
export const DefaultOptions = {
|
|
8
|
+
/** Base origin of the REST API, without the version segment. */
|
|
9
|
+
rest: 'https://api.glade.chat',
|
|
10
|
+
/** Origin of the Socket.IO gateway. */
|
|
11
|
+
gateway: 'https://ws.glade.chat',
|
|
12
|
+
/** API version path segment. */
|
|
13
|
+
version: 'v1',
|
|
14
|
+
/** Whether the REST client should auto-refresh the access token on a 401. */
|
|
15
|
+
autoRefresh: true,
|
|
16
|
+
/**
|
|
17
|
+
* How many ms before the access token's expiry to proactively refresh it.
|
|
18
|
+
* Keeps a long-running gateway connection authenticated.
|
|
19
|
+
*/
|
|
20
|
+
refreshSkewMs: 60_000,
|
|
21
|
+
/** Whether to cache structures received from REST and the gateway. */
|
|
22
|
+
cache: true,
|
|
23
|
+
/**
|
|
24
|
+
* Auto-subscribe to a House room's realtime events by emitting `room:join`.
|
|
25
|
+
* The server only auto-joins user/house/DM channels on connect, so without this
|
|
26
|
+
* the client receives no text-room message/typing/reaction events.
|
|
27
|
+
*/
|
|
28
|
+
autoSubscribeRooms: true,
|
|
29
|
+
/** Socket.IO client options merged into the gateway connection. */
|
|
30
|
+
ws: {
|
|
31
|
+
transports: ['websocket', 'polling'],
|
|
32
|
+
},
|
|
33
|
+
/** When true, the client emits verbose `debug` events. */
|
|
34
|
+
debug: false,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Client-facing event names (camelCase), emitted by {@link Client}.
|
|
39
|
+
* @enum {string}
|
|
40
|
+
*/
|
|
41
|
+
export const Events = {
|
|
42
|
+
Ready: 'ready',
|
|
43
|
+
Debug: 'debug',
|
|
44
|
+
Error: 'error',
|
|
45
|
+
Warn: 'warn',
|
|
46
|
+
Raw: 'raw',
|
|
47
|
+
|
|
48
|
+
Connecting: 'connecting',
|
|
49
|
+
Reconnecting: 'reconnecting',
|
|
50
|
+
Disconnect: 'disconnect',
|
|
51
|
+
|
|
52
|
+
MessageCreate: 'messageCreate',
|
|
53
|
+
MessageUpdate: 'messageUpdate',
|
|
54
|
+
MessageDelete: 'messageDelete',
|
|
55
|
+
MessagePinUpdate: 'messagePinUpdate',
|
|
56
|
+
MessageReactionUpdate: 'messageReactionUpdate',
|
|
57
|
+
|
|
58
|
+
TypingStart: 'typingStart',
|
|
59
|
+
PresenceUpdate: 'presenceUpdate',
|
|
60
|
+
Mention: 'mention',
|
|
61
|
+
|
|
62
|
+
RoomCreate: 'roomCreate',
|
|
63
|
+
RoomUpdate: 'roomUpdate',
|
|
64
|
+
RoomDelete: 'roomDelete',
|
|
65
|
+
RoomsReorder: 'roomsReorder',
|
|
66
|
+
|
|
67
|
+
HouseUpdate: 'houseUpdate',
|
|
68
|
+
HouseDelete: 'houseDelete',
|
|
69
|
+
MemberJoin: 'memberJoin',
|
|
70
|
+
MembersUpdate: 'membersUpdate',
|
|
71
|
+
RolesUpdate: 'rolesUpdate',
|
|
72
|
+
|
|
73
|
+
FriendRequest: 'friendRequest',
|
|
74
|
+
FriendAccepted: 'friendAccepted',
|
|
75
|
+
FriendRemoved: 'friendRemoved',
|
|
76
|
+
|
|
77
|
+
VoicePeerJoin: 'voicePeerJoin',
|
|
78
|
+
VoicePeerLeave: 'voicePeerLeave',
|
|
79
|
+
VoicePeerUpdate: 'voicePeerUpdate',
|
|
80
|
+
VoiceRoomState: 'voiceRoomState',
|
|
81
|
+
VoiceSignal: 'voiceSignal',
|
|
82
|
+
|
|
83
|
+
SessionRevoked: 'sessionRevoked',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Raw Socket.IO event names the server dispatches to the client (server → client).
|
|
88
|
+
* @enum {string}
|
|
89
|
+
*/
|
|
90
|
+
export const GatewayDispatch = {
|
|
91
|
+
Ready: 'ready',
|
|
92
|
+
MessageNew: 'message:new',
|
|
93
|
+
MessageUpdated: 'message:updated',
|
|
94
|
+
MessageDeleted: 'message:deleted',
|
|
95
|
+
MessagePinned: 'message:pinned',
|
|
96
|
+
ReactionUpdated: 'reaction:updated',
|
|
97
|
+
Mention: 'mention',
|
|
98
|
+
DmIncoming: 'dm:incoming',
|
|
99
|
+
Typing: 'typing',
|
|
100
|
+
PresenceUpdate: 'presence:update',
|
|
101
|
+
RoomCreated: 'room:created',
|
|
102
|
+
RoomUpdated: 'room:updated',
|
|
103
|
+
RoomDeleted: 'room:deleted',
|
|
104
|
+
RoomReordered: 'room:reordered',
|
|
105
|
+
HouseUpdated: 'house:updated',
|
|
106
|
+
HouseDeleted: 'house:deleted',
|
|
107
|
+
RolesUpdated: 'roles:updated',
|
|
108
|
+
MembersUpdated: 'members:updated',
|
|
109
|
+
MemberJoined: 'member:joined',
|
|
110
|
+
FriendRequest: 'friend:request',
|
|
111
|
+
FriendAccepted: 'friend:accepted',
|
|
112
|
+
FriendRemoved: 'friend:removed',
|
|
113
|
+
SessionRevoked: 'session:revoked',
|
|
114
|
+
VoicePeerJoined: 'voice:peer-joined',
|
|
115
|
+
VoicePeerLeft: 'voice:peer-left',
|
|
116
|
+
VoicePeerState: 'voice:peer-state',
|
|
117
|
+
VoiceRoomState: 'voice:room-state',
|
|
118
|
+
VoiceSignal: 'voice:signal',
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Raw Socket.IO event names the client emits to the server (client → server).
|
|
123
|
+
* @enum {string}
|
|
124
|
+
*/
|
|
125
|
+
export const GatewayCommand = {
|
|
126
|
+
RoomJoin: 'room:join',
|
|
127
|
+
RoomLeave: 'room:leave',
|
|
128
|
+
MessageSend: 'message:send',
|
|
129
|
+
MessageEdit: 'message:edit',
|
|
130
|
+
MessageDelete: 'message:delete',
|
|
131
|
+
ReactionToggle: 'reaction:toggle',
|
|
132
|
+
TypingStart: 'typing:start',
|
|
133
|
+
TypingStop: 'typing:stop',
|
|
134
|
+
PresenceSet: 'presence:set',
|
|
135
|
+
VoiceJoin: 'voice:join',
|
|
136
|
+
VoiceLeave: 'voice:leave',
|
|
137
|
+
VoiceSync: 'voice:sync',
|
|
138
|
+
VoiceSignal: 'voice:signal',
|
|
139
|
+
VoiceState: 'voice:state',
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Room (channel) types.
|
|
144
|
+
* @enum {string}
|
|
145
|
+
*/
|
|
146
|
+
export const RoomTypes = {
|
|
147
|
+
Text: 'text',
|
|
148
|
+
Voice: 'voice',
|
|
149
|
+
Portal: 'portal',
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Presence statuses a user can hold.
|
|
154
|
+
* @enum {string}
|
|
155
|
+
*/
|
|
156
|
+
export const PresenceStatus = {
|
|
157
|
+
Online: 'online',
|
|
158
|
+
Idle: 'idle',
|
|
159
|
+
DnD: 'dnd',
|
|
160
|
+
Offline: 'offline',
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/** Statuses a user may manually set (offline is implicit). */
|
|
164
|
+
export const SettableStatus = [
|
|
165
|
+
PresenceStatus.Online,
|
|
166
|
+
PresenceStatus.Idle,
|
|
167
|
+
PresenceStatus.DnD,
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Friendship statuses returned by the friends API.
|
|
172
|
+
* @enum {string}
|
|
173
|
+
*/
|
|
174
|
+
export const FriendStatus = {
|
|
175
|
+
Self: 'self',
|
|
176
|
+
None: 'none',
|
|
177
|
+
Friends: 'friends',
|
|
178
|
+
Incoming: 'incoming',
|
|
179
|
+
Outgoing: 'outgoing',
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/** Name of the httpOnly refresh-token cookie the backend sets. */
|
|
183
|
+
export const REFRESH_COOKIE = 'glade_rt';
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Glade House permission flags. These mirror the bitfield stored in
|
|
5
|
+
* `Role.permissions` and the allow/deny masks on per-room overrides server-side.
|
|
6
|
+
* @enum {number}
|
|
7
|
+
*/
|
|
8
|
+
export const PermissionFlags = {
|
|
9
|
+
/** Grants every permission and bypasses channel overrides. */
|
|
10
|
+
Administrator: 1 << 0,
|
|
11
|
+
/** Edit the House name / icon. */
|
|
12
|
+
ManageHouse: 1 << 1,
|
|
13
|
+
/** Create, edit, delete and assign roles. */
|
|
14
|
+
ManageRoles: 1 << 2,
|
|
15
|
+
/** Create, edit, delete and clone rooms, and set channel overrides. */
|
|
16
|
+
ManageChannels: 1 << 3,
|
|
17
|
+
/** Remove members from the House. */
|
|
18
|
+
KickMembers: 1 << 4,
|
|
19
|
+
/** Pin and delete other members' messages. */
|
|
20
|
+
ManageMessages: 1 << 5,
|
|
21
|
+
/** View rooms. */
|
|
22
|
+
ViewChannels: 1 << 6,
|
|
23
|
+
/** Send messages in rooms. */
|
|
24
|
+
SendMessages: 1 << 7,
|
|
25
|
+
/** Connect to voice rooms. */
|
|
26
|
+
Connect: 1 << 8,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Every permission OR'd together. */
|
|
30
|
+
export const ALL_PERMISSIONS = Object.values(PermissionFlags).reduce((a, b) => a | b, 0);
|
|
31
|
+
|
|
32
|
+
/** Permissions granted to the default `@everyone` role by the backend. */
|
|
33
|
+
export const DEFAULT_EVERYONE =
|
|
34
|
+
PermissionFlags.ViewChannels | PermissionFlags.SendMessages | PermissionFlags.Connect;
|
|
35
|
+
|
|
36
|
+
/** Permissions a per-room override is allowed to toggle. */
|
|
37
|
+
export const CHANNEL_OVERRIDABLE =
|
|
38
|
+
PermissionFlags.ViewChannels |
|
|
39
|
+
PermissionFlags.SendMessages |
|
|
40
|
+
PermissionFlags.Connect |
|
|
41
|
+
PermissionFlags.ManageMessages;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A wrapper around a Glade permission bitfield, offering ergonomic checks and edits.
|
|
45
|
+
* Accepts flag names (keys of {@link PermissionFlags}), raw numbers, or other
|
|
46
|
+
* `PermissionsBitField` instances anywhere a resolvable is expected.
|
|
47
|
+
*/
|
|
48
|
+
export class PermissionsBitField {
|
|
49
|
+
/**
|
|
50
|
+
* @param {PermissionResolvable} [bits=0]
|
|
51
|
+
*/
|
|
52
|
+
constructor(bits = 0) {
|
|
53
|
+
/** @type {number} The raw bitfield. */
|
|
54
|
+
this.bitfield = PermissionsBitField.resolve(bits);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resolves a permission-like value into a raw number.
|
|
59
|
+
* @param {PermissionResolvable} bits
|
|
60
|
+
* @returns {number}
|
|
61
|
+
*/
|
|
62
|
+
static resolve(bits = 0) {
|
|
63
|
+
if (typeof bits === 'number') return bits;
|
|
64
|
+
if (bits instanceof PermissionsBitField) return bits.bitfield;
|
|
65
|
+
if (typeof bits === 'string') {
|
|
66
|
+
const flag = PermissionFlags[bits];
|
|
67
|
+
if (flag === undefined) throw new RangeError(`Unknown permission flag: ${bits}`);
|
|
68
|
+
return flag;
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(bits)) {
|
|
71
|
+
return bits.map((b) => PermissionsBitField.resolve(b)).reduce((a, b) => a | b, 0);
|
|
72
|
+
}
|
|
73
|
+
throw new TypeError(`Cannot resolve permissions from: ${typeof bits}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Whether the bitfield holds all of the given permissions. The `Administrator`
|
|
78
|
+
* flag short-circuits to `true`, matching the server's `has()` semantics.
|
|
79
|
+
* @param {PermissionResolvable} permission
|
|
80
|
+
* @param {boolean} [checkAdmin=true]
|
|
81
|
+
* @returns {boolean}
|
|
82
|
+
*/
|
|
83
|
+
has(permission, checkAdmin = true) {
|
|
84
|
+
if (checkAdmin && (this.bitfield & PermissionFlags.Administrator) !== 0) return true;
|
|
85
|
+
const bit = PermissionsBitField.resolve(permission);
|
|
86
|
+
return (this.bitfield & bit) === bit;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Returns whether any of the given permission bits are present.
|
|
91
|
+
* @param {PermissionResolvable} permission
|
|
92
|
+
* @returns {boolean}
|
|
93
|
+
*/
|
|
94
|
+
any(permission) {
|
|
95
|
+
const bit = PermissionsBitField.resolve(permission);
|
|
96
|
+
return (this.bitfield & bit) !== 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Adds permissions, returning this for chaining.
|
|
101
|
+
* @param {...PermissionResolvable} bits
|
|
102
|
+
* @returns {this}
|
|
103
|
+
*/
|
|
104
|
+
add(...bits) {
|
|
105
|
+
for (const b of bits) this.bitfield |= PermissionsBitField.resolve(b);
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Removes permissions, returning this for chaining.
|
|
111
|
+
* @param {...PermissionResolvable} bits
|
|
112
|
+
* @returns {this}
|
|
113
|
+
*/
|
|
114
|
+
remove(...bits) {
|
|
115
|
+
for (const b of bits) this.bitfield &= ~PermissionsBitField.resolve(b);
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The flag names currently set in this bitfield.
|
|
121
|
+
* @returns {string[]}
|
|
122
|
+
*/
|
|
123
|
+
toArray() {
|
|
124
|
+
return Object.keys(PermissionFlags).filter(
|
|
125
|
+
(name) => (this.bitfield & PermissionFlags[name]) === PermissionFlags[name],
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** @returns {number} */
|
|
130
|
+
valueOf() {
|
|
131
|
+
return this.bitfield;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** @returns {string[]} */
|
|
135
|
+
toJSON() {
|
|
136
|
+
return this.toArray();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
PermissionsBitField.Flags = PermissionFlags;
|
|
141
|
+
PermissionsBitField.All = ALL_PERMISSIONS;
|
|
142
|
+
PermissionsBitField.DefaultEveryone = DEFAULT_EVERYONE;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @typedef {keyof typeof PermissionFlags | number | PermissionsBitField | Array<keyof typeof PermissionFlags | number | PermissionsBitField>} PermissionResolvable
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
export default PermissionsBitField;
|
package/src/util/Util.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generates a short, unlikely-to-collide nonce used to correlate an optimistically
|
|
5
|
+
* sent message with its `message:new` echo from the gateway.
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
export function generateNonce() {
|
|
9
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Decodes the payload of a JWT without verifying its signature. Used only to read
|
|
14
|
+
* the access token's `exp` so the client can refresh proactively.
|
|
15
|
+
* @param {string} token
|
|
16
|
+
* @returns {Record<string, any> | null}
|
|
17
|
+
*/
|
|
18
|
+
export function decodeJwt(token) {
|
|
19
|
+
try {
|
|
20
|
+
const part = token.split('.')[1];
|
|
21
|
+
if (!part) return null;
|
|
22
|
+
const json = Buffer.from(part.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8');
|
|
23
|
+
return JSON.parse(json);
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Builds a query string (with leading `?`) from an object, skipping
|
|
31
|
+
* `undefined`/`null` values. Returns `''` when there is nothing to encode.
|
|
32
|
+
* @param {Record<string, any>} [query]
|
|
33
|
+
* @returns {string}
|
|
34
|
+
*/
|
|
35
|
+
export function makeQuery(query) {
|
|
36
|
+
if (!query) return '';
|
|
37
|
+
const params = new URLSearchParams();
|
|
38
|
+
for (const [key, value] of Object.entries(query)) {
|
|
39
|
+
if (value === undefined || value === null) continue;
|
|
40
|
+
params.append(key, String(value));
|
|
41
|
+
}
|
|
42
|
+
const str = params.toString();
|
|
43
|
+
return str ? `?${str}` : '';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Trims any trailing slashes from a URL/origin.
|
|
48
|
+
* @param {string} url
|
|
49
|
+
* @returns {string}
|
|
50
|
+
*/
|
|
51
|
+
export function trimTrailingSlash(url) {
|
|
52
|
+
return String(url).replace(/\/+$/, '');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolves something to its id: accepts a raw id string or any object with an `id`.
|
|
57
|
+
* @param {string | { id: string }} value
|
|
58
|
+
* @returns {string}
|
|
59
|
+
*/
|
|
60
|
+
export function resolveId(value) {
|
|
61
|
+
return typeof value === 'string' ? value : value?.id;
|
|
62
|
+
}
|