@borealise/pipeline 1.0.0-alpha.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/dist/index.js ADDED
@@ -0,0 +1,518 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Activity: () => Activity,
24
+ CloseCodes: () => CloseCodes,
25
+ Events: () => Events,
26
+ Opcodes: () => Opcodes,
27
+ PipelineClient: () => PipelineClient,
28
+ PipelineErrors: () => PipelineErrors,
29
+ Presence: () => Presence,
30
+ Roles: () => Roles,
31
+ createPipeline: () => createPipeline,
32
+ getEventName: () => getEventName,
33
+ getPipelineErrorName: () => getPipelineErrorName
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/constants/opcodes.ts
38
+ var Opcodes = {
39
+ IDENTIFY: 0,
40
+ HEARTBEAT: 1,
41
+ PRESENCE_UPDATE: 2,
42
+ SUBSCRIBE: 3,
43
+ UNSUBSCRIBE: 4,
44
+ REQUEST: 5,
45
+ CHAT_SEND: 6,
46
+ HELLO: 16,
47
+ HEARTBEAT_ACK: 17,
48
+ READY: 18,
49
+ INVALID_SESSION: 19,
50
+ RECONNECT: 20,
51
+ DISPATCH: 21,
52
+ ERROR: 255
53
+ };
54
+ var Events = {
55
+ USER_UPDATE: 0,
56
+ USER_PRESENCE_UPDATE: 1,
57
+ USER_TYPING: 2,
58
+ USER_LEVEL_UP: 3,
59
+ SESSION_CREATE: 16,
60
+ SESSION_DELETE: 17,
61
+ SESSION_UPDATE: 18,
62
+ NOTIFICATION_CREATE: 32,
63
+ NOTIFICATION_DELETE: 33,
64
+ ROOM_JOIN: 48,
65
+ ROOM_LEAVE: 49,
66
+ ROOM_UPDATE: 50,
67
+ ROOM_DELETE: 51,
68
+ ROOM_USER_JOIN: 64,
69
+ ROOM_USER_LEAVE: 65,
70
+ ROOM_USER_KICK: 66,
71
+ ROOM_USER_BAN: 67,
72
+ ROOM_USER_MUTE: 68,
73
+ ROOM_USER_UNMUTE: 69,
74
+ ROOM_USER_ROLE_UPDATE: 70,
75
+ ROOM_USER_AVATAR_UPDATE: 71,
76
+ ROOM_USER_SUBSCRIPTION_UPDATE: 72,
77
+ ROOM_CHAT_MESSAGE: 80,
78
+ ROOM_CHAT_DELETE: 81,
79
+ ROOM_DJ_ADVANCE: 96,
80
+ ROOM_DJ_UPDATE: 97,
81
+ ROOM_WAITLIST_JOIN: 98,
82
+ ROOM_WAITLIST_LEAVE: 99,
83
+ ROOM_WAITLIST_UPDATE: 100,
84
+ ROOM_WAITLIST_LOCK: 101,
85
+ ROOM_WAITLIST_CYCLE: 102,
86
+ ROOM_TIME_SYNC: 103,
87
+ ROOM_VOTE: 112,
88
+ ROOM_GRAB: 113,
89
+ FRIEND_REQUEST: 128,
90
+ FRIEND_REQUEST_CANCEL: 129,
91
+ FRIEND_ACCEPT: 130,
92
+ FRIEND_REMOVE: 131,
93
+ SYSTEM_MESSAGE: 240,
94
+ MAINTENANCE: 241,
95
+ RATE_LIMIT: 242
96
+ };
97
+ var Presence = {
98
+ ONLINE: 0,
99
+ IDLE: 1,
100
+ DND: 2,
101
+ INVISIBLE: 3,
102
+ OFFLINE: 4
103
+ };
104
+ var Activity = {
105
+ NONE: 0,
106
+ VIEWING: 1,
107
+ EDITING: 2,
108
+ IDLE: 3,
109
+ STREAMING: 4,
110
+ LISTENING: 5,
111
+ WATCHING: 6,
112
+ CUSTOM: 255
113
+ };
114
+ var Roles = {
115
+ USER: 0,
116
+ MODERATOR: 1,
117
+ ADMIN: 2,
118
+ OWNER: 255
119
+ };
120
+ var CloseCodes = {
121
+ NORMAL: 1e3,
122
+ GOING_AWAY: 1001,
123
+ PROTOCOL_ERROR: 1002,
124
+ UNKNOWN_ERROR: 4e3,
125
+ UNKNOWN_OPCODE: 4001,
126
+ DECODE_ERROR: 4002,
127
+ NOT_AUTHENTICATED: 4003,
128
+ AUTHENTICATION_FAILED: 4004,
129
+ ALREADY_AUTHENTICATED: 4005,
130
+ INVALID_SESSION: 4006,
131
+ RATE_LIMITED: 4008,
132
+ SESSION_TIMEOUT: 4009,
133
+ SERVER_SHUTDOWN: 4010
134
+ };
135
+ var PipelineErrors = {
136
+ CHAT_MESSAGE_EMPTY: 4100,
137
+ CHAT_MESSAGE_TOO_LONG: 4101,
138
+ CHAT_ROOM_NOT_FOUND: 4102,
139
+ CHAT_NOT_IN_ROOM: 4103,
140
+ CHAT_USER_MUTED: 4104,
141
+ ROOM_NOT_FOUND: 4200,
142
+ ROOM_NOT_ACTIVE: 4201,
143
+ ROOM_ALREADY_MEMBER: 4202,
144
+ ROOM_NOT_MEMBER: 4203,
145
+ ROOM_BANNED: 4204,
146
+ ROOM_FULL: 4205,
147
+ WAITLIST_LOCKED: 4300,
148
+ WAITLIST_FULL: 4301,
149
+ WAITLIST_ALREADY_IN: 4302,
150
+ WAITLIST_NOT_IN: 4303,
151
+ VOTE_INVALID: 4400,
152
+ VOTE_ALREADY_VOTED: 4401,
153
+ VOTE_NO_TRACK: 4402
154
+ };
155
+ function getPipelineErrorName(code) {
156
+ return Object.entries(PipelineErrors).find(([, value]) => value === code)?.[0] || "UNKNOWN_ERROR";
157
+ }
158
+ function getEventName(code) {
159
+ return Object.entries(Events).find(([, value]) => value === code)?.[0] || "UNKNOWN";
160
+ }
161
+
162
+ // src/logger.ts
163
+ var _Logger = class _Logger {
164
+ constructor(name, options = {}) {
165
+ this.name = name;
166
+ this.options = options;
167
+ }
168
+ static create(name, options = {}) {
169
+ const logger = new _Logger(name, options);
170
+ _Logger.loggers.set(name, logger);
171
+ return logger;
172
+ }
173
+ enabled(level) {
174
+ return level >= (this.options.minLevel ?? 0 /* DEBUG */);
175
+ }
176
+ debug(message, ...args) {
177
+ if (!this.enabled(0 /* DEBUG */)) return;
178
+ console.log(`[DEBUG] [${this.name}] ${message}`, ...args);
179
+ }
180
+ info(message, ...args) {
181
+ if (!this.enabled(1 /* INFO */)) return;
182
+ console.info(`[INFO] [${this.name}] ${message}`, ...args);
183
+ }
184
+ warn(message, ...args) {
185
+ if (!this.enabled(2 /* WARN */)) return;
186
+ console.warn(`[WARN] [${this.name}] ${message}`, ...args);
187
+ }
188
+ error(message, ...args) {
189
+ if (!this.enabled(3 /* ERROR */)) return;
190
+ console.error(`[ERROR] [${this.name}] ${message}`, ...args);
191
+ }
192
+ };
193
+ _Logger.loggers = /* @__PURE__ */ new Map();
194
+ var Logger = _Logger;
195
+
196
+ // src/PipelineClient.ts
197
+ var PipelineClient = class {
198
+ constructor(options) {
199
+ this.ws = null;
200
+ this.sessionId = null;
201
+ this.heartbeatInterval = null;
202
+ this.heartbeatTimer = null;
203
+ this.reconnectTimer = null;
204
+ this.reconnectAttempts = 0;
205
+ this.lastSequence = 0;
206
+ this.subscriptions = /* @__PURE__ */ new Set();
207
+ this._state = "disconnected";
208
+ this._user = null;
209
+ this.eventListeners = /* @__PURE__ */ new Map();
210
+ this.connectionListeners = /* @__PURE__ */ new Map();
211
+ this.dispatchHandler = null;
212
+ this.maxReconnectAttempts = 10;
213
+ this.reconnectBackoff = [1e3, 2e3, 5e3, 1e4, 3e4];
214
+ this.options = { ...options };
215
+ this.logger = Logger.create(options.loggerName || "Pipeline");
216
+ }
217
+ get state() {
218
+ return this._state;
219
+ }
220
+ get user() {
221
+ return this._user;
222
+ }
223
+ get isConnected() {
224
+ return this._state === "connected" || this._state === "identified";
225
+ }
226
+ get isIdentified() {
227
+ return this._state === "identified";
228
+ }
229
+ setDispatchHandler(handler) {
230
+ this.dispatchHandler = handler;
231
+ }
232
+ connect() {
233
+ if (!this.options.url) {
234
+ this.logger.error("Cannot connect: missing pipeline url");
235
+ return;
236
+ }
237
+ if (this.ws && (this.ws.readyState === WebSocket.CONNECTING || this.ws.readyState === WebSocket.OPEN)) {
238
+ this.logger.warn("Already connected or connecting");
239
+ return;
240
+ }
241
+ this.setState("connecting");
242
+ try {
243
+ const factory = this.options.webSocketFactory || ((url) => new WebSocket(url));
244
+ this.ws = factory(this.options.url);
245
+ this.ws.onopen = () => this.handleOpen();
246
+ this.ws.onmessage = (event) => this.handleMessage(event);
247
+ this.ws.onclose = (event) => this.handleClose(event);
248
+ this.ws.onerror = (event) => this.handleError(event);
249
+ } catch (error) {
250
+ this.logger.error("Connection failed", error);
251
+ this.scheduleReconnect();
252
+ }
253
+ }
254
+ disconnect() {
255
+ this.clearTimers();
256
+ this.reconnectAttempts = 0;
257
+ if (this.ws) {
258
+ this.ws.close(CloseCodes.NORMAL, "client disconnect");
259
+ this.ws = null;
260
+ }
261
+ this.sessionId = null;
262
+ this._user = null;
263
+ this.setState("disconnected");
264
+ }
265
+ identify(token) {
266
+ this.send(Opcodes.IDENTIFY, { token });
267
+ }
268
+ updatePresence(status, activity) {
269
+ this.send(Opcodes.PRESENCE_UPDATE, { status, activity });
270
+ }
271
+ subscribe(events) {
272
+ for (const event of events) {
273
+ this.subscriptions.add(event);
274
+ }
275
+ if (this.isIdentified) {
276
+ this.send(Opcodes.SUBSCRIBE, { events });
277
+ }
278
+ }
279
+ unsubscribe(events) {
280
+ for (const event of events) {
281
+ this.subscriptions.delete(event);
282
+ }
283
+ if (this.isIdentified) {
284
+ this.send(Opcodes.UNSUBSCRIBE, { events });
285
+ }
286
+ }
287
+ sendChatMessage(roomSlug, content) {
288
+ if (!this.isIdentified) {
289
+ this.logger.warn("Cannot send chat: not identified");
290
+ return false;
291
+ }
292
+ this.send(Opcodes.CHAT_SEND, {
293
+ room_slug: roomSlug,
294
+ content
295
+ });
296
+ return true;
297
+ }
298
+ on(event, listener) {
299
+ if (!this.eventListeners.has(event)) {
300
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
301
+ }
302
+ this.eventListeners.get(event).add(listener);
303
+ return () => this.off(event, listener);
304
+ }
305
+ off(event, listener) {
306
+ this.eventListeners.get(event)?.delete(listener);
307
+ }
308
+ onConnection(event, listener) {
309
+ if (!this.connectionListeners.has(event)) {
310
+ this.connectionListeners.set(event, /* @__PURE__ */ new Set());
311
+ }
312
+ this.connectionListeners.get(event).add(listener);
313
+ return () => this.offConnection(event, listener);
314
+ }
315
+ offConnection(event, listener) {
316
+ this.connectionListeners.get(event)?.delete(listener);
317
+ }
318
+ setState(state) {
319
+ if (this._state === state) return;
320
+ this._state = state;
321
+ this.emit("onStateChange", state);
322
+ this.dispatchHandler?.("pipeline/setConnectionState", state);
323
+ }
324
+ clearTimers() {
325
+ if (this.heartbeatTimer !== null) {
326
+ clearInterval(this.heartbeatTimer);
327
+ this.heartbeatTimer = null;
328
+ }
329
+ if (this.reconnectTimer !== null) {
330
+ clearTimeout(this.reconnectTimer);
331
+ this.reconnectTimer = null;
332
+ }
333
+ }
334
+ handleOpen() {
335
+ this.logger.info("Connected");
336
+ this.setState("connected");
337
+ this.reconnectAttempts = 0;
338
+ this.emit("onConnect");
339
+ }
340
+ handleMessage(event) {
341
+ try {
342
+ const message = JSON.parse(String(event.data));
343
+ switch (message.op) {
344
+ case Opcodes.HELLO:
345
+ this.handleHello(message.d);
346
+ break;
347
+ case Opcodes.HEARTBEAT_ACK:
348
+ break;
349
+ case Opcodes.READY:
350
+ this.handleReady(message.d);
351
+ break;
352
+ case Opcodes.INVALID_SESSION:
353
+ this.handleInvalidSession(message.d);
354
+ break;
355
+ case Opcodes.RECONNECT:
356
+ this.handleReconnect();
357
+ break;
358
+ case Opcodes.DISPATCH:
359
+ this.handleDispatch(message.t, message.d, message.s);
360
+ break;
361
+ case Opcodes.ERROR:
362
+ this.handleServerError(message.d);
363
+ break;
364
+ default:
365
+ this.logger.warn(`Unknown opcode: ${message.op}`);
366
+ }
367
+ } catch (error) {
368
+ this.logger.error("Failed to parse message", error);
369
+ }
370
+ }
371
+ handleClose(event) {
372
+ this.logger.info(`Disconnected: ${event.code} - ${event.reason}`);
373
+ this.clearTimers();
374
+ this.ws = null;
375
+ this.emit("onDisconnect", event.code, event.reason);
376
+ const noReconnectCodes = [
377
+ CloseCodes.AUTHENTICATION_FAILED,
378
+ CloseCodes.NOT_AUTHENTICATED,
379
+ CloseCodes.NORMAL
380
+ ];
381
+ if (!noReconnectCodes.includes(event.code)) {
382
+ this.scheduleReconnect();
383
+ return;
384
+ }
385
+ this.setState("disconnected");
386
+ }
387
+ handleError(_event) {
388
+ this.logger.error("WebSocket error");
389
+ }
390
+ handleHello(payload) {
391
+ this.sessionId = payload.session_id;
392
+ this.heartbeatInterval = payload.heartbeat_interval;
393
+ this.startHeartbeat();
394
+ const token = this.resolveToken();
395
+ if (token) {
396
+ this.identify(token);
397
+ }
398
+ }
399
+ handleReady(payload) {
400
+ this._user = payload.user;
401
+ this.setState("identified");
402
+ this.emit("onReady", payload);
403
+ this.dispatchHandler?.("pipeline/setReady", payload);
404
+ if (this.subscriptions.size > 0) {
405
+ this.subscribe(Array.from(this.subscriptions));
406
+ }
407
+ }
408
+ handleInvalidSession(payload) {
409
+ if (!payload.resumable) {
410
+ this._user = null;
411
+ this.setState("connected");
412
+ this.dispatchHandler?.("pipeline/setInvalidSession");
413
+ }
414
+ }
415
+ handleReconnect() {
416
+ this.logger.info("Server requested reconnect");
417
+ this.disconnect();
418
+ this.connect();
419
+ }
420
+ handleDispatch(event, data, sequence) {
421
+ if (typeof sequence === "number") {
422
+ this.lastSequence = sequence;
423
+ }
424
+ this.emitEvent(event, data);
425
+ this.emit("onDispatch", event, data);
426
+ this.dispatchHandler?.("pipeline/handleDispatch", { event, data });
427
+ }
428
+ handleServerError(payload) {
429
+ this.logger.error(`Server error: ${payload.code} - ${payload.message || "unknown"}`);
430
+ this.emit("onError", payload);
431
+ this.dispatchHandler?.("pipeline/handleServerError", payload);
432
+ }
433
+ startHeartbeat() {
434
+ if (this.heartbeatTimer !== null) {
435
+ clearInterval(this.heartbeatTimer);
436
+ }
437
+ if (!this.heartbeatInterval) return;
438
+ const jitter = this.heartbeatInterval * 0.1 * (Math.random() * 2 - 1);
439
+ const interval = this.heartbeatInterval + jitter;
440
+ this.heartbeatTimer = window.setInterval(() => {
441
+ this.sendHeartbeat();
442
+ }, interval);
443
+ this.sendHeartbeat();
444
+ }
445
+ sendHeartbeat() {
446
+ this.send(Opcodes.HEARTBEAT, {
447
+ seq: this.lastSequence || null
448
+ });
449
+ }
450
+ scheduleReconnect() {
451
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
452
+ this.logger.error("Max reconnect attempts reached");
453
+ this.setState("disconnected");
454
+ return;
455
+ }
456
+ this.setState("reconnecting");
457
+ const backoffIndex = Math.min(this.reconnectAttempts, this.reconnectBackoff.length - 1);
458
+ const delay = this.reconnectBackoff[backoffIndex];
459
+ this.reconnectTimer = window.setTimeout(() => {
460
+ this.reconnectAttempts += 1;
461
+ this.emit("onReconnect", this.reconnectAttempts);
462
+ this.connect();
463
+ }, delay);
464
+ }
465
+ send(op, data) {
466
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
467
+ this.logger.warn("Cannot send: not connected");
468
+ return;
469
+ }
470
+ const message = { op, d: data };
471
+ this.ws.send(JSON.stringify(message));
472
+ }
473
+ emitEvent(event, data) {
474
+ const listeners = this.eventListeners.get(event);
475
+ if (!listeners) return;
476
+ for (const listener of listeners) {
477
+ try {
478
+ listener(data);
479
+ } catch (error) {
480
+ this.logger.error(`Event listener error for ${event}`, error);
481
+ }
482
+ }
483
+ }
484
+ emit(event, ...args) {
485
+ const listeners = this.connectionListeners.get(event);
486
+ if (!listeners) return;
487
+ for (const listener of listeners) {
488
+ try {
489
+ ;
490
+ listener(...args);
491
+ } catch (error) {
492
+ this.logger.error(`Connection listener error for ${event}`, error);
493
+ }
494
+ }
495
+ }
496
+ resolveToken() {
497
+ const fromProvider = this.options.tokenProvider?.();
498
+ if (!fromProvider) return null;
499
+ return fromProvider;
500
+ }
501
+ };
502
+ function createPipeline(options) {
503
+ return new PipelineClient(options);
504
+ }
505
+ // Annotate the CommonJS export names for ESM import in node:
506
+ 0 && (module.exports = {
507
+ Activity,
508
+ CloseCodes,
509
+ Events,
510
+ Opcodes,
511
+ PipelineClient,
512
+ PipelineErrors,
513
+ Presence,
514
+ Roles,
515
+ createPipeline,
516
+ getEventName,
517
+ getPipelineErrorName
518
+ });