exguard-backend 1.0.33 → 1.0.35

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.cjs CHANGED
@@ -210,16 +210,20 @@ var ExGuardCache = class {
210
210
  keys.forEach((key) => this.notifySubscribers(key));
211
211
  }
212
212
  /**
213
- * Clear cache for a specific user
213
+ * Clear cache for a specific user - clears ALL caches related to this user
214
214
  */
215
215
  clearUserCache(userId) {
216
216
  const keysToDelete = Array.from(this.cache.keys()).filter(
217
- (key) => key.startsWith(`user:${userId}:`)
217
+ (key) => key.includes(userId)
218
218
  );
219
+ console.log(`[ExGuardCache] Clearing cache for user ${userId}, found ${keysToDelete.length} keys:`, keysToDelete);
219
220
  keysToDelete.forEach((key) => {
220
221
  this.cache.delete(key);
221
222
  this.notifySubscribers(key);
222
223
  });
224
+ if (keysToDelete.length === 0) {
225
+ console.log(`[ExGuardCache] No cache entries found for user ${userId}`);
226
+ }
223
227
  }
224
228
  /**
225
229
  * Subscribe to cache changes
@@ -283,25 +287,16 @@ var ExGuardCache = class {
283
287
  var cache = new ExGuardCache();
284
288
 
285
289
  // src/realtime.ts
286
- var WebSocketImpl;
287
- if (typeof window === "undefined") {
288
- try {
289
- WebSocketImpl = require("ws");
290
- } catch {
291
- WebSocketImpl = null;
292
- }
293
- } else {
294
- WebSocketImpl = WebSocket;
295
- }
290
+ var import_socket = require("socket.io-client");
296
291
  var ExGuardRealtime = class {
297
292
  handlers = /* @__PURE__ */ new Map();
298
- websocket = null;
293
+ socket = null;
299
294
  reconnectAttempts = 0;
300
295
  config;
301
296
  currentUrl = null;
302
297
  currentToken = null;
298
+ currentUserId = null;
303
299
  shouldReconnect = true;
304
- isNode = typeof window === "undefined";
305
300
  constructor(config = {}) {
306
301
  this.config = {
307
302
  autoReconnect: config.autoReconnect ?? true,
@@ -319,13 +314,14 @@ var ExGuardRealtime = class {
319
314
  /**
320
315
  * Initialize and optionally connect to realtime server
321
316
  */
322
- async init(url, accessToken) {
317
+ async init(url, accessToken, userId) {
323
318
  if (url) {
324
319
  this.currentUrl = url;
325
320
  this.currentToken = accessToken ?? null;
321
+ this.currentUserId = userId ?? null;
326
322
  if (this.config.autoConnect) {
327
323
  try {
328
- await this.connect(url, accessToken);
324
+ await this.connect(url, accessToken, userId);
329
325
  } catch (error) {
330
326
  console.warn("[ExGuardRealtime] Auto-connect failed, will retry on demand:", error);
331
327
  }
@@ -333,142 +329,131 @@ var ExGuardRealtime = class {
333
329
  }
334
330
  }
335
331
  /**
336
- * Connect to realtime server
337
- * @param url - WebSocket URL (e.g., wss://api.example.com/realtime)
338
- * @param auth - Authentication options (accessToken, apiKey, bearerToken) - optional
339
- * @param silent - If true, won't throw error on connection failure
332
+ * Connect to realtime server using Socket.IO
333
+ * @param url - Server URL (e.g., https://api.example.com)
334
+ * @param auth - Authentication options (accessToken, apiKey, bearerToken, userId)
340
335
  */
341
- connect(url, auth, silent = false) {
336
+ connect(url, auth, userId) {
342
337
  let authToken;
343
- let authType = null;
338
+ let authUserId;
344
339
  if (typeof auth === "string") {
345
340
  authToken = auth;
346
- authType = "access_token";
347
- } else if (auth?.accessToken) {
348
- authToken = auth.accessToken;
349
- authType = "access_token";
350
- } else if (auth?.apiKey) {
351
- authToken = auth.apiKey;
352
- authType = "api_key";
353
- } else if (auth?.bearerToken) {
354
- authToken = auth.bearerToken;
355
- authType = "bearer";
341
+ authUserId = userId || "unknown";
342
+ } else if (typeof auth === "object") {
343
+ authToken = auth.accessToken || auth.bearerToken || auth.apiKey;
344
+ authUserId = auth.userId || userId || "unknown";
356
345
  }
357
346
  return new Promise((resolve, reject) => {
358
347
  this.shouldReconnect = true;
359
348
  this.currentUrl = url;
360
349
  this.currentToken = authToken ?? null;
361
- if (!WebSocketImpl) {
362
- const err = new Error('WebSocket is not available. Install "ws" package for Node.js: npm install ws');
363
- this.config.onError(err);
364
- if (!silent) reject(err);
365
- return;
366
- }
350
+ this.currentUserId = authUserId ?? null;
367
351
  try {
368
- let wsUrl = url;
369
- if (authToken && authType) {
370
- if (authType === "bearer") {
371
- wsUrl = `${url}?bearer=${encodeURIComponent(authToken)}`;
372
- } else {
373
- wsUrl = `${url}?${authType}=${encodeURIComponent(authToken)}`;
374
- }
375
- }
376
- if (this.isNode && WebSocketImpl === require("ws")) {
377
- this.websocket = new WebSocketImpl(wsUrl);
378
- this.setupNodeWebSocket(resolve, reject);
379
- } else {
380
- this.websocket = new WebSocketImpl(wsUrl);
381
- this.setupBrowserWebSocket(resolve, reject);
352
+ if (this.socket) {
353
+ this.socket.disconnect();
354
+ this.socket = null;
382
355
  }
356
+ const socketUrl = `${url}/realtime`;
357
+ console.log("[ExGuardRealtime] Connecting to:", socketUrl);
358
+ this.socket = (0, import_socket.io)(socketUrl, {
359
+ auth: {
360
+ token: authToken,
361
+ userId: authUserId
362
+ },
363
+ transports: ["websocket", "polling"],
364
+ reconnection: this.config.autoReconnect,
365
+ reconnectionDelay: this.config.reconnectDelay,
366
+ reconnectionDelayMax: this.config.reconnectMaxDelay,
367
+ reconnectionAttempts: this.config.maxReconnectAttempts,
368
+ timeout: 2e4
369
+ });
370
+ this.socket.on("connect", () => {
371
+ this.reconnectAttempts = 0;
372
+ console.log("[ExGuardRealtime] Connected! Socket ID:", this.socket?.id);
373
+ this.config.onConnect();
374
+ resolve();
375
+ });
376
+ this.socket.on("disconnect", (reason) => {
377
+ console.log("[ExGuardRealtime] Disconnected:", reason);
378
+ this.config.onDisconnect();
379
+ if (this.shouldReconnect && this.config.autoReconnect) {
380
+ this.handleReconnect();
381
+ }
382
+ });
383
+ this.socket.on("connect_error", (error) => {
384
+ console.error("[ExGuardRealtime] Connection error:", error.message);
385
+ const err = new Error(`Connection error: ${error.message}`);
386
+ this.config.onError(err);
387
+ if (!this.shouldReconnect || this.reconnectAttempts >= this.config.maxReconnectAttempts) {
388
+ reject(err);
389
+ }
390
+ });
391
+ this.socket.on("error", (error) => {
392
+ console.error("[ExGuardRealtime] Socket error:", error);
393
+ const err = new Error(`Socket error: ${error}`);
394
+ this.config.onError(err);
395
+ });
396
+ this.socket.on("reconnect_attempt", (attempt) => {
397
+ this.reconnectAttempts = attempt;
398
+ console.log(`[ExGuardRealtime] Reconnecting... (attempt ${attempt})`);
399
+ });
400
+ this.socket.on("reconnect", () => {
401
+ console.log("[ExGuardRealtime] Reconnected!");
402
+ });
403
+ this.socket.on("reconnect_failed", () => {
404
+ console.error("[ExGuardRealtime] Reconnection failed after max attempts");
405
+ const err = new Error("Reconnection failed after maximum attempts");
406
+ this.config.onError(err);
407
+ reject(err);
408
+ });
409
+ this.socket.onAny((eventName, payload) => {
410
+ try {
411
+ const realtimeEvent = {
412
+ type: eventName,
413
+ timestamp: payload?.timestamp || Date.now(),
414
+ data: payload?.data || payload,
415
+ userId: payload?.userId
416
+ };
417
+ this.handleEvent(realtimeEvent);
418
+ } catch (error) {
419
+ console.error("[ExGuardRealtime] Failed to handle event:", error);
420
+ }
421
+ });
422
+ setTimeout(() => {
423
+ if (!this.socket?.connected) {
424
+ const err = new Error("Connection timeout");
425
+ this.config.onError(err);
426
+ this.socket?.disconnect();
427
+ reject(err);
428
+ }
429
+ }, 2e4);
383
430
  } catch (error) {
384
431
  reject(error);
385
432
  }
386
433
  });
387
434
  }
388
- setupBrowserWebSocket(resolve, reject) {
389
- const timeout = setTimeout(() => {
390
- reject(new Error("Connection timeout"));
391
- }, 1e4);
392
- this.websocket.onopen = () => {
393
- clearTimeout(timeout);
394
- this.reconnectAttempts = 0;
395
- console.log("[ExGuardRealtime] Connected to realtime server");
396
- this.config.onConnect();
397
- resolve();
398
- };
399
- this.websocket.onmessage = (event) => {
400
- try {
401
- const realtimeEvent = JSON.parse(event.data);
402
- this.handleEvent(realtimeEvent);
403
- } catch (error) {
404
- console.error("[ExGuardRealtime] Failed to parse event:", error);
405
- }
406
- };
407
- this.websocket.onclose = () => {
408
- clearTimeout(timeout);
409
- console.log("[ExGuardRealtime] Disconnected from realtime server");
410
- this.config.onDisconnect();
411
- this.handleReconnect();
412
- };
413
- this.websocket.onerror = (error) => {
414
- clearTimeout(timeout);
415
- console.error("[ExGuardRealtime] WebSocket error:", error);
416
- const err = new Error("WebSocket connection error");
417
- this.config.onError(err);
418
- if (!this.shouldReconnect) {
419
- reject(err);
420
- }
421
- };
422
- }
423
- setupNodeWebSocket(resolve, reject) {
424
- const timeout = setTimeout(() => {
425
- reject(new Error("Connection timeout"));
426
- }, 1e4);
427
- this.websocket.on("open", () => {
428
- clearTimeout(timeout);
429
- this.reconnectAttempts = 0;
430
- console.log("[ExGuardRealtime] Connected to realtime server (Node.js)");
431
- this.config.onConnect();
432
- resolve();
433
- });
434
- this.websocket.on("message", (data) => {
435
- try {
436
- const realtimeEvent = JSON.parse(data.toString());
437
- this.handleEvent(realtimeEvent);
438
- } catch (error) {
439
- console.error("[ExGuardRealtime] Failed to parse event:", error);
440
- }
441
- });
442
- this.websocket.on("close", () => {
443
- clearTimeout(timeout);
444
- console.log("[ExGuardRealtime] Disconnected from realtime server");
445
- this.config.onDisconnect();
446
- this.handleReconnect();
447
- });
448
- this.websocket.on("error", (error) => {
449
- clearTimeout(timeout);
450
- console.error("[ExGuardRealtime] WebSocket error:", error);
451
- const err = new Error("WebSocket connection error: " + (error?.message || error));
452
- this.config.onError(err);
453
- if (this.shouldReconnect) {
454
- this.handleReconnect();
455
- } else {
456
- reject(err);
457
- }
458
- });
435
+ /**
436
+ * Subscribe to channels
437
+ */
438
+ subscribeToChannel(channel) {
439
+ if (this.socket?.connected) {
440
+ this.socket.emit(`subscribe:${channel}`);
441
+ console.log(`[ExGuardRealtime] Subscribed to channel: ${channel}`);
442
+ }
459
443
  }
460
444
  /**
461
445
  * Disconnect from realtime server
462
446
  */
463
447
  disconnect() {
464
448
  this.shouldReconnect = false;
465
- if (this.websocket) {
466
- this.websocket.close();
467
- this.websocket = null;
449
+ if (this.socket) {
450
+ this.socket.disconnect();
451
+ this.socket = null;
468
452
  }
469
453
  }
470
454
  /**
471
455
  * Subscribe to realtime events
456
+ * Use '*' as eventType to receive ALL events
472
457
  */
473
458
  subscribe(eventType, handler) {
474
459
  if (!this.handlers.has(eventType)) {
@@ -486,14 +471,10 @@ var ExGuardRealtime = class {
486
471
  };
487
472
  }
488
473
  /**
489
- * Subscribe to all realtime events
474
+ * Subscribe to ALL realtime events (wildcard)
490
475
  */
491
476
  subscribeAll(handler) {
492
- const eventTypes = ["rbac_update", "user_update", "role_update", "permission_update"];
493
- const unsubscribes = eventTypes.map((type) => this.subscribe(type, handler));
494
- return () => {
495
- unsubscribes.forEach((unsub) => unsub());
496
- };
477
+ return this.subscribe("*", handler);
497
478
  }
498
479
  /**
499
480
  * Handle incoming realtime events
@@ -510,6 +491,16 @@ var ExGuardRealtime = class {
510
491
  }
511
492
  });
512
493
  }
494
+ const wildcardHandlers = this.handlers.get("*");
495
+ if (wildcardHandlers) {
496
+ wildcardHandlers.forEach((handler) => {
497
+ try {
498
+ handler(event);
499
+ } catch (error) {
500
+ console.error("[ExGuardRealtime] Wildcard handler error:", error);
501
+ }
502
+ });
503
+ }
513
504
  }
514
505
  /**
515
506
  * Handle reconnection logic
@@ -526,7 +517,7 @@ var ExGuardRealtime = class {
526
517
  );
527
518
  console.log(`[ExGuardRealtime] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
528
519
  setTimeout(() => {
529
- this.connect(this.currentUrl, this.currentToken).catch((error) => {
520
+ this.connect(this.currentUrl, this.currentToken, this.currentUserId).catch((error) => {
530
521
  console.error("[ExGuardRealtime] Reconnection failed:", error);
531
522
  });
532
523
  }, delay);
@@ -540,7 +531,7 @@ var ExGuardRealtime = class {
540
531
  reconnect() {
541
532
  if (this.currentUrl && this.currentToken) {
542
533
  this.reconnectAttempts = 0;
543
- return this.connect(this.currentUrl, this.currentToken);
534
+ return this.connect(this.currentUrl, this.currentToken, this.currentUserId || void 0);
544
535
  } else if (this.currentUrl) {
545
536
  this.reconnectAttempts = 0;
546
537
  return this.connect(this.currentUrl);
@@ -548,51 +539,25 @@ var ExGuardRealtime = class {
548
539
  return Promise.reject(new Error("No URL configured"));
549
540
  }
550
541
  /**
551
- * Check if connected
552
- */
542
+ * Check if connected
543
+ */
553
544
  isConnected() {
554
- if (!this.websocket) return false;
555
- if (this.isNode) {
556
- return this.websocket.readyState === 1;
557
- }
558
- return this.websocket.readyState === WebSocket.OPEN;
545
+ return this.socket?.connected ?? false;
559
546
  }
560
547
  /**
561
548
  * Get connection status
562
549
  */
563
550
  getStatus() {
564
- if (!this.websocket) return "disconnected";
565
- if (this.isNode) {
566
- switch (this.websocket.readyState) {
567
- case 0:
568
- return "connecting";
569
- case 1:
570
- return "connected";
571
- case 2:
572
- case 3:
573
- return "disconnected";
574
- default:
575
- return "disconnected";
576
- }
577
- }
578
- switch (this.websocket.readyState) {
579
- case WebSocket.CONNECTING:
580
- return "connecting";
581
- case WebSocket.OPEN:
582
- return "connected";
583
- case WebSocket.CLOSING:
584
- case WebSocket.CLOSED:
585
- return "disconnected";
586
- default:
587
- return "disconnected";
588
- }
551
+ if (!this.socket) return "disconnected";
552
+ if (this.socket.connected) return "connected";
553
+ return "disconnected";
589
554
  }
590
555
  /**
591
556
  * Send a message to the server
592
557
  */
593
- send(event) {
594
- if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
595
- this.websocket.send(JSON.stringify(event));
558
+ send(event, data) {
559
+ if (this.socket?.connected) {
560
+ this.socket.emit(event, data);
596
561
  }
597
562
  }
598
563
  /**
@@ -608,9 +573,9 @@ var ExGuardRealtime = class {
608
573
  * Connect on-demand with user's access token from request
609
574
  * Call this in your guard or middleware with the user's token
610
575
  */
611
- async connectWithUserToken(url, accessToken) {
576
+ async connectWithUserToken(url, accessToken, userId) {
612
577
  try {
613
- await this.connect(url, accessToken, true);
578
+ await this.connect(url, accessToken, userId);
614
579
  } catch (error) {
615
580
  console.warn("[ExGuardRealtime] Failed to connect with user token:", error);
616
581
  }
@@ -683,40 +648,42 @@ var ExGuardBackendEnhanced = class {
683
648
  try {
684
649
  await this.realtime.connect(
685
650
  this.config.realtime.url,
686
- this.config.realtime.accessToken,
687
- true
688
- // silent mode - won't throw on connection failure
651
+ this.config.realtime.accessToken
689
652
  );
690
- this.realtime.subscribe("rbac_update", (event) => {
691
- console.log("[ExGuardBackend] RBAC update received:", event);
692
- this.config.realtime?.onRBACUpdate?.(event);
693
- if (event.userId) {
694
- this.cache.clearUserCache(event.userId);
695
- } else {
696
- this.cache.clear();
653
+ this.realtime.subscribeToChannel("rbac");
654
+ this.realtime.subscribeToChannel("roles");
655
+ this.realtime.subscribeToChannel("permissions");
656
+ this.realtime.subscribe("*", (event) => {
657
+ console.log("[ExGuardBackend] Realtime event received:", event);
658
+ if (event.type.includes("rbac") || event.type === "rbac_update") {
659
+ this.config.realtime?.onRBACUpdate?.(event);
660
+ if (event.userId) {
661
+ this.cache.clearUserCache(event.userId);
662
+ } else {
663
+ this.cache.clear();
664
+ }
697
665
  }
698
- });
699
- this.realtime.subscribe("user_update", (event) => {
700
- console.log("[ExGuardBackend] User update received:", event);
701
- this.config.realtime?.onUserUpdate?.(event);
702
- if (event.userId) {
703
- this.cache.clearUserCache(event.userId);
666
+ if (event.type.includes("user") || event.type === "user_update") {
667
+ this.config.realtime?.onUserUpdate?.(event);
668
+ if (event.userId) {
669
+ this.cache.clearUserCache(event.userId);
670
+ } else {
671
+ this.cache.clear();
672
+ }
704
673
  }
705
- });
706
- this.realtime.subscribe("role_update", (event) => {
707
- console.log("[ExGuardBackend] Role update received:", event);
708
- if (event.userId) {
709
- this.cache.clearUserCache(event.userId);
710
- } else {
711
- this.cache.clear();
674
+ if (event.type.includes("role") || event.type === "role_update") {
675
+ if (event.userId) {
676
+ this.cache.clearUserCache(event.userId);
677
+ } else {
678
+ this.cache.clear();
679
+ }
712
680
  }
713
- });
714
- this.realtime.subscribe("permission_update", (event) => {
715
- console.log("[ExGuardBackend] Permission update received:", event);
716
- if (event.userId) {
717
- this.cache.clearUserCache(event.userId);
718
- } else {
719
- this.cache.clear();
681
+ if (event.type.includes("permission") || event.type === "permission_update") {
682
+ if (event.userId) {
683
+ this.cache.clearUserCache(event.userId);
684
+ } else {
685
+ this.cache.clear();
686
+ }
720
687
  }
721
688
  });
722
689
  } catch (error) {