@eleven-am/pondsocket-client 0.0.2

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.
@@ -0,0 +1,34 @@
1
+ import { Channel } from '../core/channel';
2
+ import { JoinParams } from '../types';
3
+
4
+ export default class PondClient {
5
+ constructor (endpoint: string, params?: Record<string, any>);
6
+
7
+ /**
8
+ * @desc Connects to the server and returns the socket.
9
+ */
10
+ connect (backoff?: number): void;
11
+
12
+ /**
13
+ * @desc Returns the current state of the socket.
14
+ */
15
+ getState (): boolean;
16
+
17
+ /**
18
+ * @desc Disconnects the socket.
19
+ */
20
+ disconnect (): void;
21
+
22
+ /**
23
+ * @desc Creates a channel with the given name and params.
24
+ * @param name - The name of the channel.
25
+ * @param params - The params to send to the server.
26
+ */
27
+ createChannel (name: string, params?: JoinParams): Channel<import('../types').PondEventMap>;
28
+
29
+ /**
30
+ * @desc Subscribes to the connection state.
31
+ * @param callback - The callback to call when the state changes.
32
+ */
33
+ onConnectionChange (callback: (state: boolean) => void): import('@eleven-am/pondsocket-common/subjects/types').Unsubscribe;
34
+ }
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _PondClient_instances, _PondClient_channels, _PondClient_createPublisher;
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const pondsocket_common_1 = require("@eleven-am/pondsocket-common");
16
+ const channel_1 = require("../core/channel");
17
+ class PondClient {
18
+ constructor(endpoint, params = {}) {
19
+ _PondClient_instances.add(this);
20
+ _PondClient_channels.set(this, void 0);
21
+ let address;
22
+ try {
23
+ address = new URL(endpoint);
24
+ }
25
+ catch (e) {
26
+ address = new URL(window.location.toString());
27
+ address.pathname = endpoint;
28
+ }
29
+ const query = new URLSearchParams(params);
30
+ address.search = query.toString();
31
+ const protocol = address.protocol === 'https:' ? 'wss:' : 'ws:';
32
+ if (address.protocol !== 'wss:' && address.protocol !== 'ws:') {
33
+ address.protocol = protocol;
34
+ }
35
+ this._address = address;
36
+ __classPrivateFieldSet(this, _PondClient_channels, {}, "f");
37
+ this._broadcaster = new pondsocket_common_1.SimpleSubject();
38
+ this._connectionState = new pondsocket_common_1.SimpleBehaviorSubject(false);
39
+ }
40
+ /**
41
+ * @desc Connects to the server and returns the socket.
42
+ */
43
+ connect(backoff = 1) {
44
+ const socket = new WebSocket(this._address.toString());
45
+ socket.onopen = () => {
46
+ this._connectionState.publish(true);
47
+ };
48
+ socket.onmessage = (message) => {
49
+ const data = JSON.parse(message.data);
50
+ this._broadcaster.publish(data);
51
+ };
52
+ socket.onerror = () => {
53
+ this._connectionState.publish(false);
54
+ setTimeout(() => {
55
+ this.connect(backoff * 2);
56
+ }, backoff * 1000);
57
+ };
58
+ this._socket = socket;
59
+ }
60
+ /**
61
+ * @desc Returns the current state of the socket.
62
+ */
63
+ getState() {
64
+ return this._connectionState.value;
65
+ }
66
+ /**
67
+ * @desc Disconnects the socket.
68
+ */
69
+ disconnect() {
70
+ var _a;
71
+ Object.values(__classPrivateFieldGet(this, _PondClient_channels, "f")).forEach((channel) => channel.leave());
72
+ this._connectionState.publish(false);
73
+ (_a = this._socket) === null || _a === void 0 ? void 0 : _a.close();
74
+ __classPrivateFieldSet(this, _PondClient_channels, {}, "f");
75
+ }
76
+ /**
77
+ * @desc Creates a channel with the given name and params.
78
+ * @param name - The name of the channel.
79
+ * @param params - The params to send to the server.
80
+ */
81
+ createChannel(name, params) {
82
+ if (__classPrivateFieldGet(this, _PondClient_channels, "f")[name] && __classPrivateFieldGet(this, _PondClient_channels, "f")[name].channelState !== pondsocket_common_1.ChannelState.CLOSED) {
83
+ return __classPrivateFieldGet(this, _PondClient_channels, "f")[name];
84
+ }
85
+ const publisher = __classPrivateFieldGet(this, _PondClient_instances, "m", _PondClient_createPublisher).call(this);
86
+ const channel = new channel_1.Channel(publisher, this._connectionState, name, this._broadcaster, params || {});
87
+ __classPrivateFieldGet(this, _PondClient_channels, "f")[name] = channel;
88
+ return channel;
89
+ }
90
+ /**
91
+ * @desc Subscribes to the connection state.
92
+ * @param callback - The callback to call when the state changes.
93
+ */
94
+ onConnectionChange(callback) {
95
+ return this._connectionState.subscribe(callback);
96
+ }
97
+ }
98
+ _PondClient_channels = new WeakMap(), _PondClient_instances = new WeakSet(), _PondClient_createPublisher = function _PondClient_createPublisher() {
99
+ return (message) => {
100
+ if (this._connectionState.value) {
101
+ this._socket.send(JSON.stringify(message));
102
+ }
103
+ };
104
+ };
105
+ exports.default = PondClient;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const pondsocket_common_1 = require("@eleven-am/pondsocket-common");
16
+ const client_1 = __importDefault(require("./client"));
17
+ class MockWebSocket {
18
+ // eslint-disable-next-line no-useless-constructor
19
+ constructor(url) {
20
+ this.url = url;
21
+ this.send = jest.fn();
22
+ this.close = jest.fn();
23
+ }
24
+ }
25
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
26
+ // @ts-expect-error
27
+ global.WebSocket = MockWebSocket;
28
+ describe('PondClient', () => {
29
+ let pondClient;
30
+ beforeEach(() => {
31
+ pondClient = new client_1.default('ws://example.com');
32
+ });
33
+ afterEach(() => {
34
+ jest.clearAllMocks();
35
+ });
36
+ test('connect method should set up WebSocket events', () => {
37
+ pondClient.connect();
38
+ const mockWebSocket = pondClient['_socket'];
39
+ expect(mockWebSocket.onopen).toBeInstanceOf(Function);
40
+ expect(mockWebSocket.onmessage).toBeInstanceOf(Function);
41
+ expect(mockWebSocket.onerror).toBeInstanceOf(Function);
42
+ });
43
+ test('disconnect method should close the socket and leave all channels', () => {
44
+ const mockCallback = jest.fn();
45
+ pondClient.onConnectionChange(mockCallback);
46
+ pondClient.connect();
47
+ const mockWebSocket = pondClient['_socket'];
48
+ mockWebSocket.onopen();
49
+ expect(mockCallback).toHaveBeenCalledWith(true);
50
+ const channel = pondClient.createChannel('exampleChannel');
51
+ const spyOnLeave = jest.spyOn(channel, 'leave');
52
+ mockCallback.mockClear();
53
+ pondClient.disconnect();
54
+ expect(mockCallback).toHaveBeenCalledWith(false);
55
+ expect(mockWebSocket.close).toHaveBeenCalled();
56
+ expect(spyOnLeave).toHaveBeenCalled();
57
+ });
58
+ test('createChannel method should create a new channel or return an existing one', () => {
59
+ const mockChannel = pondClient.createChannel('exampleChannel');
60
+ const mockExistingChannel = pondClient.createChannel('exampleChannel');
61
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
62
+ expect(mockChannel).toBeInstanceOf(require('../core/channel').Channel);
63
+ expect(mockExistingChannel).toBe(mockChannel);
64
+ });
65
+ test('onConnectionChange method should subscribe to connection state changes', () => {
66
+ const mockCallback = jest.fn();
67
+ const unsubscribe = pondClient.onConnectionChange(mockCallback);
68
+ pondClient['_connectionState'].publish(true);
69
+ expect(mockCallback).toHaveBeenCalledWith(true);
70
+ unsubscribe();
71
+ pondClient['_connectionState'].publish(false);
72
+ // Should not be called again after unsubscribe
73
+ expect(mockCallback).toHaveBeenCalledTimes(1);
74
+ });
75
+ test('getState method should return the current state of the socket', () => {
76
+ const initialState = pondClient.getState();
77
+ expect(initialState).toBe(false);
78
+ pondClient['_connectionState'].publish(true);
79
+ const updatedState = pondClient.getState();
80
+ expect(updatedState).toBe(true);
81
+ });
82
+ test('publish method should send a message to the server', () => {
83
+ pondClient.connect();
84
+ const mockWebSocket = pondClient['_socket'];
85
+ const channel = pondClient.createChannel('exampleChannel');
86
+ mockWebSocket.onopen();
87
+ channel.join();
88
+ expect(mockWebSocket.send).toHaveBeenCalledTimes(1);
89
+ const sentObject = mockWebSocket.send.mock.calls[0][0];
90
+ expect(sentObject).toBeDefined();
91
+ expect(JSON.parse(sentObject)).toEqual(expect.objectContaining({
92
+ addresses: pondsocket_common_1.ChannelReceiver.ALL_USERS,
93
+ action: pondsocket_common_1.ClientActions.JOIN_CHANNEL,
94
+ event: pondsocket_common_1.ClientActions.JOIN_CHANNEL,
95
+ payload: {},
96
+ channelName: 'exampleChannel',
97
+ }));
98
+ });
99
+ it('onError method should reconnect to the server', () => __awaiter(void 0, void 0, void 0, function* () {
100
+ const connectSpy = jest.spyOn(pondClient, 'connect');
101
+ pondClient.connect();
102
+ let mockWebSocket = pondClient['_socket'];
103
+ expect(connectSpy).toHaveBeenCalledTimes(1);
104
+ connectSpy.mockClear();
105
+ mockWebSocket.onerror();
106
+ yield new Promise((resolve) => setTimeout(resolve, 1000));
107
+ expect(connectSpy).toHaveBeenCalledTimes(1);
108
+ connectSpy.mockClear();
109
+ mockWebSocket = pondClient['_socket'];
110
+ mockWebSocket.onerror();
111
+ yield new Promise((resolve) => setTimeout(resolve, 2000));
112
+ expect(connectSpy).toHaveBeenCalledTimes(1);
113
+ }));
114
+ test('it should publish messages received from the server', () => {
115
+ pondClient.connect();
116
+ const mockWebSocket = pondClient['_socket'];
117
+ const broadcasterSpy = jest.spyOn(pondClient['_broadcaster'], 'publish');
118
+ mockWebSocket.onopen();
119
+ mockWebSocket.onmessage({ data: JSON.stringify({ event: 'exampleEvent' }) });
120
+ expect(broadcasterSpy).toHaveBeenCalledTimes(1);
121
+ expect(broadcasterSpy).toHaveBeenCalledWith({ event: 'exampleEvent' });
122
+ });
123
+ });
@@ -0,0 +1,125 @@
1
+ import { ChannelState } from '@eleven-am/pondsocket-common';
2
+ import {
3
+ PondPresence,
4
+ PondMessage,
5
+ PresencePayload,
6
+ PondEventMap,
7
+ EventWithResponse,
8
+ PayloadForResponse,
9
+ ResponseForEvent,
10
+ } from '../types';
11
+
12
+ export declare class Channel<EventMap extends PondEventMap = PondEventMap> {
13
+ /**
14
+ * @desc Gets the current connection state of the channel.
15
+ */
16
+ get channelState (): ChannelState;
17
+
18
+ /**
19
+ * @desc Connects to the channel.
20
+ */
21
+ join (): void;
22
+
23
+ /**
24
+ * @desc Disconnects from the channel.
25
+ */
26
+ leave (): void;
27
+
28
+ /**
29
+ * @desc Monitors the channel for messages.
30
+ * @param callback - The callback to call when a message is received.
31
+ */
32
+ onMessage<Event extends keyof EventMap> (callback: (event: Event, message: EventMap[Event]) => void): import('@eleven-am/pondsocket-common/subjects/types').Unsubscribe;
33
+
34
+ /**
35
+ * @desc Monitors the channel for messages.
36
+ * @param event - The event to monitor.
37
+ * @param callback - The callback to call when a message is received.
38
+ */
39
+ onMessageEvent<Event extends keyof EventMap> (event: Event, callback: (message: EventMap[Event]) => void): import('@eleven-am/pondsocket-common/subjects/types').Unsubscribe;
40
+
41
+ /**
42
+ * @desc Monitors the channel state of the channel.
43
+ * @param callback - The callback to call when the connection state changes.
44
+ */
45
+ onChannelStateChange (callback: (connected: ChannelState) => void): import('@eleven-am/pondsocket-common/subjects/types').Unsubscribe;
46
+
47
+ /**
48
+ * @desc Detects when clients join the channel.
49
+ * @param callback - The callback to call when a client joins the channel.
50
+ */
51
+ onJoin (callback: (presence: PondPresence) => void): import('@eleven-am/pondsocket-common/subjects/types').Unsubscribe;
52
+
53
+ /**
54
+ * @desc Detects when clients leave the channel.
55
+ * @param callback - The callback to call when a client leaves the channel.
56
+ */
57
+ onLeave (callback: (presence: PondPresence) => void): import('@eleven-am/pondsocket-common/subjects/types').Unsubscribe;
58
+
59
+ /**
60
+ * @desc Detects when clients change their presence in the channel.
61
+ * @param callback - The callback to call when a client changes their presence in the channel.
62
+ */
63
+ onPresenceChange (callback: (presence: PresencePayload) => void): import('@eleven-am/pondsocket-common/subjects/types').Unsubscribe;
64
+
65
+ /**
66
+ * @desc Sends a message to specific clients in the channel.
67
+ * @param event - The event to send.
68
+ * @param payload - The message to send.
69
+ * @param recipient - The clients to send the message to.
70
+ */
71
+ sendMessage (event: string, payload: PondMessage, recipient: string[]): void;
72
+
73
+ /**
74
+ * @desc Sends a message to the server and waits for a response.
75
+ * @param sentEvent - The event to send.
76
+ * @param payload - The message to send.
77
+ */
78
+ sendForResponse<Event extends EventWithResponse<EventMap>> (sentEvent: Event, payload: PayloadForResponse<EventMap, Event>): Promise<ResponseForEvent<EventMap, Event>>;
79
+
80
+ /**
81
+ * @desc Broadcasts a message to every other client in the channel except yourself.
82
+ * @param event - The event to send.
83
+ * @param payload - The message to send.
84
+ */
85
+ broadcastFrom<Event extends keyof EventMap> (event: Event, payload: EventMap[Event]): void;
86
+
87
+ /**
88
+ * @desc Broadcasts a message to the channel, including yourself.
89
+ * @param event - The event to send.
90
+ * @param payload - The message to send.
91
+ */
92
+ broadcast<Event extends keyof EventMap> (event: Event, payload: EventMap[Event]): void;
93
+
94
+ /**
95
+ * @desc Gets the current presence of the channel.
96
+ */
97
+ getPresence (): PondPresence[];
98
+
99
+ /**
100
+ * @desc Monitors the presence of the channel.
101
+ * @param callback - The callback to call when the presence changes.
102
+ */
103
+ onUsersChange (callback: (users: PondPresence[]) => void): import('@eleven-am/pondsocket-common/subjects/types').Unsubscribe;
104
+
105
+ /**
106
+ * @desc Checks if the channel is connected.
107
+ */
108
+ isConnected (): boolean;
109
+
110
+ /**
111
+ * @desc Checks if the channel is stalled.
112
+ */
113
+ isStalled (): boolean;
114
+
115
+ /**
116
+ * @desc Checks if the channel is closed.
117
+ */
118
+ isClosed (): boolean;
119
+
120
+ /**
121
+ * @desc Monitors the connection state of the channel.
122
+ * @param callback - The callback to call when the connection state changes.
123
+ */
124
+ onConnectionChange (callback: (connected: boolean) => void): import('@eleven-am/pondsocket-common/subjects/types').Unsubscribe;
125
+ }