@kispi/chat 0.1.0 → 0.1.1

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
@@ -782,6 +782,8 @@ var ChatClient = class extends Emitter {
782
782
  nextFrameId = 0;
783
783
  opening;
784
784
  rest;
785
+ /** Registered DOM event listener cleanups (visibilitychange, online). */
786
+ teardowns = [];
785
787
  /**
786
788
  * Room handles this client has handed out, keyed by how they were
787
789
  * asked for. Nothing is ever removed: a handle a consumer holds has to
@@ -818,6 +820,57 @@ var ChatClient = class extends Emitter {
818
820
  token: async () => this.lastToken,
819
821
  fetch: options.fetch ?? globalThis.fetch.bind(globalThis)
820
822
  });
823
+ this.setupLifecycle();
824
+ }
825
+ setupLifecycle() {
826
+ if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
827
+ const onVisibilityChange = () => {
828
+ if (document.visibilityState === "visible") {
829
+ void this.resume();
830
+ }
831
+ };
832
+ document.addEventListener("visibilitychange", onVisibilityChange);
833
+ this.teardowns.push(() => document.removeEventListener("visibilitychange", onVisibilityChange));
834
+ }
835
+ if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
836
+ const onOnline = () => {
837
+ void this.resume();
838
+ };
839
+ window.addEventListener("online", onOnline);
840
+ this.teardowns.push(() => window.removeEventListener("online", onOnline));
841
+ }
842
+ }
843
+ /**
844
+ * Resumes the connection and syncs all active room subscriptions.
845
+ *
846
+ * Called automatically in browsers on `visibilitychange` (tab returning
847
+ * from background) and `online` (network reconnected), or manually by
848
+ * consumers when waking from sleep.
849
+ *
850
+ * - If closed by caller: does nothing.
851
+ * - If disconnected or reconnecting: cancels any backoff delay and
852
+ * reconnects immediately.
853
+ * - If already open: sends a ping frame to detect dead sockets, and
854
+ * triggers `resume()` on every room handle so missed messages are
855
+ * gap-filled via REST.
856
+ */
857
+ async resume() {
858
+ if (this.closedByCaller) return;
859
+ if (this.state !== "open" || this.socket === void 0) {
860
+ this.clearTimers();
861
+ return this.connect();
862
+ }
863
+ try {
864
+ this.socket.send(JSON.stringify({ type: "ping", id: `p${++this.nextFrameId}` }));
865
+ } catch {
866
+ this.clearTimers();
867
+ return this.connect();
868
+ }
869
+ for (const room of this.handles.values()) {
870
+ void room.resume().catch((err) => {
871
+ room.emit("error", err instanceof Error ? err : new Error(String(err)));
872
+ });
873
+ }
821
874
  }
822
875
  /** The account-level routes: the rooms this user is in, and unread. */
823
876
  get rooms() {
@@ -926,6 +979,13 @@ var ChatClient = class extends Emitter {
926
979
  */
927
980
  async close() {
928
981
  this.closedByCaller = true;
982
+ this.teardowns.forEach((t) => {
983
+ try {
984
+ t();
985
+ } catch {
986
+ }
987
+ });
988
+ this.teardowns.length = 0;
929
989
  this.clearTimers();
930
990
  const socket = this.socket;
931
991
  this.socket = void 0;
package/dist/index.d.cts CHANGED
@@ -119,6 +119,16 @@ type Message = {
119
119
  lastSeq?: number;
120
120
  };
121
121
  };
122
+ type ReactionUser = {
123
+ userId?: string;
124
+ name?: string;
125
+ emoji: string;
126
+ withdrawn?: boolean;
127
+ };
128
+ type ReactionsPage = {
129
+ reactions: ReactionUser[];
130
+ cursor: string;
131
+ };
122
132
  type SendInput = {
123
133
  text?: string;
124
134
  attachments?: unknown[];
@@ -336,7 +346,7 @@ declare class Room extends Emitter<RoomEvents> {
336
346
  emoji?: string;
337
347
  cursor?: string;
338
348
  limit?: number;
339
- }): Promise<unknown>;
349
+ }): Promise<ReactionsPage>;
340
350
  /**
341
351
  * Re-subscribes after a reconnect. Called by the client.
342
352
  *
@@ -459,6 +469,8 @@ declare class ChatClient extends Emitter<ChatClientEvents> {
459
469
  private nextFrameId;
460
470
  private opening;
461
471
  private readonly rest;
472
+ /** Registered DOM event listener cleanups (visibilitychange, online). */
473
+ private readonly teardowns;
462
474
  /**
463
475
  * Room handles this client has handed out, keyed by how they were
464
476
  * asked for. Nothing is ever removed: a handle a consumer holds has to
@@ -480,6 +492,22 @@ declare class ChatClient extends Emitter<ChatClientEvents> {
480
492
  /** True once a socket has reached `hello`, so the next one is a return. */
481
493
  private everOpened;
482
494
  constructor(options: ChatClientOptions);
495
+ private setupLifecycle;
496
+ /**
497
+ * Resumes the connection and syncs all active room subscriptions.
498
+ *
499
+ * Called automatically in browsers on `visibilitychange` (tab returning
500
+ * from background) and `online` (network reconnected), or manually by
501
+ * consumers when waking from sleep.
502
+ *
503
+ * - If closed by caller: does nothing.
504
+ * - If disconnected or reconnecting: cancels any backoff delay and
505
+ * reconnects immediately.
506
+ * - If already open: sends a ping frame to detect dead sockets, and
507
+ * triggers `resume()` on every room handle so missed messages are
508
+ * gap-filled via REST.
509
+ */
510
+ resume(): Promise<void>;
483
511
  /** The account-level routes: the rooms this user is in, and unread. */
484
512
  get rooms(): {
485
513
  list: (options?: {
@@ -803,4 +831,4 @@ declare class Timeline {
803
831
  }
804
832
  declare function createTimeline(options: TimelineOptions): Timeline;
805
833
 
806
- export { type AuthUser, ChatClient, type ChatClientEvents, type ChatClientOptions, ChatError, type ConnectionState, type FetchRange, type Frame, type Hello, type Message, type PublishAck, Room, type RoomEvents, type SendInput, type SendOptions, Timeline, type TimelineOptions, type WebSocketFactory, type WebSocketLike, createChatClient, createTimeline };
834
+ export { type AuthUser, ChatClient, type ChatClientEvents, type ChatClientOptions, ChatError, type ConnectionState, type FetchRange, type Frame, type Hello, type Message, type PublishAck, type ReactionUser, type ReactionsPage, Room, type RoomEvents, type SendInput, type SendOptions, Timeline, type TimelineOptions, type WebSocketFactory, type WebSocketLike, createChatClient, createTimeline };
package/dist/index.d.ts CHANGED
@@ -119,6 +119,16 @@ type Message = {
119
119
  lastSeq?: number;
120
120
  };
121
121
  };
122
+ type ReactionUser = {
123
+ userId?: string;
124
+ name?: string;
125
+ emoji: string;
126
+ withdrawn?: boolean;
127
+ };
128
+ type ReactionsPage = {
129
+ reactions: ReactionUser[];
130
+ cursor: string;
131
+ };
122
132
  type SendInput = {
123
133
  text?: string;
124
134
  attachments?: unknown[];
@@ -336,7 +346,7 @@ declare class Room extends Emitter<RoomEvents> {
336
346
  emoji?: string;
337
347
  cursor?: string;
338
348
  limit?: number;
339
- }): Promise<unknown>;
349
+ }): Promise<ReactionsPage>;
340
350
  /**
341
351
  * Re-subscribes after a reconnect. Called by the client.
342
352
  *
@@ -459,6 +469,8 @@ declare class ChatClient extends Emitter<ChatClientEvents> {
459
469
  private nextFrameId;
460
470
  private opening;
461
471
  private readonly rest;
472
+ /** Registered DOM event listener cleanups (visibilitychange, online). */
473
+ private readonly teardowns;
462
474
  /**
463
475
  * Room handles this client has handed out, keyed by how they were
464
476
  * asked for. Nothing is ever removed: a handle a consumer holds has to
@@ -480,6 +492,22 @@ declare class ChatClient extends Emitter<ChatClientEvents> {
480
492
  /** True once a socket has reached `hello`, so the next one is a return. */
481
493
  private everOpened;
482
494
  constructor(options: ChatClientOptions);
495
+ private setupLifecycle;
496
+ /**
497
+ * Resumes the connection and syncs all active room subscriptions.
498
+ *
499
+ * Called automatically in browsers on `visibilitychange` (tab returning
500
+ * from background) and `online` (network reconnected), or manually by
501
+ * consumers when waking from sleep.
502
+ *
503
+ * - If closed by caller: does nothing.
504
+ * - If disconnected or reconnecting: cancels any backoff delay and
505
+ * reconnects immediately.
506
+ * - If already open: sends a ping frame to detect dead sockets, and
507
+ * triggers `resume()` on every room handle so missed messages are
508
+ * gap-filled via REST.
509
+ */
510
+ resume(): Promise<void>;
483
511
  /** The account-level routes: the rooms this user is in, and unread. */
484
512
  get rooms(): {
485
513
  list: (options?: {
@@ -803,4 +831,4 @@ declare class Timeline {
803
831
  }
804
832
  declare function createTimeline(options: TimelineOptions): Timeline;
805
833
 
806
- export { type AuthUser, ChatClient, type ChatClientEvents, type ChatClientOptions, ChatError, type ConnectionState, type FetchRange, type Frame, type Hello, type Message, type PublishAck, Room, type RoomEvents, type SendInput, type SendOptions, Timeline, type TimelineOptions, type WebSocketFactory, type WebSocketLike, createChatClient, createTimeline };
834
+ export { type AuthUser, ChatClient, type ChatClientEvents, type ChatClientOptions, ChatError, type ConnectionState, type FetchRange, type Frame, type Hello, type Message, type PublishAck, type ReactionUser, type ReactionsPage, Room, type RoomEvents, type SendInput, type SendOptions, Timeline, type TimelineOptions, type WebSocketFactory, type WebSocketLike, createChatClient, createTimeline };
package/dist/index.js CHANGED
@@ -727,6 +727,8 @@ var ChatClient = class extends Emitter {
727
727
  nextFrameId = 0;
728
728
  opening;
729
729
  rest;
730
+ /** Registered DOM event listener cleanups (visibilitychange, online). */
731
+ teardowns = [];
730
732
  /**
731
733
  * Room handles this client has handed out, keyed by how they were
732
734
  * asked for. Nothing is ever removed: a handle a consumer holds has to
@@ -763,6 +765,57 @@ var ChatClient = class extends Emitter {
763
765
  token: async () => this.lastToken,
764
766
  fetch: options.fetch ?? globalThis.fetch.bind(globalThis)
765
767
  });
768
+ this.setupLifecycle();
769
+ }
770
+ setupLifecycle() {
771
+ if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
772
+ const onVisibilityChange = () => {
773
+ if (document.visibilityState === "visible") {
774
+ void this.resume();
775
+ }
776
+ };
777
+ document.addEventListener("visibilitychange", onVisibilityChange);
778
+ this.teardowns.push(() => document.removeEventListener("visibilitychange", onVisibilityChange));
779
+ }
780
+ if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
781
+ const onOnline = () => {
782
+ void this.resume();
783
+ };
784
+ window.addEventListener("online", onOnline);
785
+ this.teardowns.push(() => window.removeEventListener("online", onOnline));
786
+ }
787
+ }
788
+ /**
789
+ * Resumes the connection and syncs all active room subscriptions.
790
+ *
791
+ * Called automatically in browsers on `visibilitychange` (tab returning
792
+ * from background) and `online` (network reconnected), or manually by
793
+ * consumers when waking from sleep.
794
+ *
795
+ * - If closed by caller: does nothing.
796
+ * - If disconnected or reconnecting: cancels any backoff delay and
797
+ * reconnects immediately.
798
+ * - If already open: sends a ping frame to detect dead sockets, and
799
+ * triggers `resume()` on every room handle so missed messages are
800
+ * gap-filled via REST.
801
+ */
802
+ async resume() {
803
+ if (this.closedByCaller) return;
804
+ if (this.state !== "open" || this.socket === void 0) {
805
+ this.clearTimers();
806
+ return this.connect();
807
+ }
808
+ try {
809
+ this.socket.send(JSON.stringify({ type: "ping", id: `p${++this.nextFrameId}` }));
810
+ } catch {
811
+ this.clearTimers();
812
+ return this.connect();
813
+ }
814
+ for (const room of this.handles.values()) {
815
+ void room.resume().catch((err) => {
816
+ room.emit("error", err instanceof Error ? err : new Error(String(err)));
817
+ });
818
+ }
766
819
  }
767
820
  /** The account-level routes: the rooms this user is in, and unread. */
768
821
  get rooms() {
@@ -871,6 +924,13 @@ var ChatClient = class extends Emitter {
871
924
  */
872
925
  async close() {
873
926
  this.closedByCaller = true;
927
+ this.teardowns.forEach((t) => {
928
+ try {
929
+ t();
930
+ } catch {
931
+ }
932
+ });
933
+ this.teardowns.length = 0;
874
934
  this.clearTimers();
875
935
  const socket = this.socket;
876
936
  this.socket = void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kispi/chat",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Client SDK for the chat server: one WebSocket, many rooms, ordered history.",
5
5
  "license": "MIT",
6
6
  "type": "module",