pingerchips-js 2.0.0 → 2.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.
Files changed (3) hide show
  1. package/README.md +57 -46
  2. package/index.js +82 -51
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Pingerchips JavaScript SDK
1
+ # pingerchips-js
2
2
 
3
3
  Real-time WebSocket client for Pingerchips.
4
4
 
@@ -10,17 +10,29 @@ npm install pingerchips-js
10
10
 
11
11
  ## Usage
12
12
 
13
- ### Basic Connection
13
+ ### Basic connection
14
14
 
15
15
  ```javascript
16
16
  import Pingerchips from 'pingerchips-js';
17
17
 
18
+ // Connects to queue.pingerchips.com in production by default
19
+ const client = new Pingerchips('your_app_key');
20
+ ```
21
+
22
+ Pass `debug: "false"` to force the production endpoint, or override with `endpoint`:
23
+
24
+ ```javascript
25
+ const client = new Pingerchips('your_app_key', {
26
+ debug: "false" // uses wss://queue.pingerchips.com/socket
27
+ });
28
+
29
+ // or explicit:
18
30
  const client = new Pingerchips('your_app_key', {
19
- endpoint: 'wss://pinger-processor.pingerchips.com/socket'
31
+ endpoint: 'wss://your-own-deployment.com/socket'
20
32
  });
21
33
  ```
22
34
 
23
- ### Subscribe to Public Channels
35
+ ### Public channels
24
36
 
25
37
  ```javascript
26
38
  const channel = await client.subscribe('lobby');
@@ -29,77 +41,76 @@ channel.bind('message', (data) => {
29
41
  console.log('Received:', data);
30
42
  });
31
43
 
44
+ // Send client event
32
45
  channel.trigger('message', { text: 'Hello!' });
33
46
  ```
34
47
 
35
- ### Subscribe to Private Channels
48
+ ### Private channels
36
49
 
37
- For private channels, you need to configure an auth endpoint:
50
+ Requires an `authEndpoint` on your server (see `pingerchips-js-server`):
38
51
 
39
52
  ```javascript
40
53
  const client = new Pingerchips('your_app_key', {
41
- endpoint: 'wss://pinger-processor.pingerchips.com/socket',
42
54
  authEndpoint: 'https://your-server.com/auth',
43
- authInfo: {
44
- userId: '123',
45
- token: 'user-session-token'
46
- }
55
+ authInfo: { token: 'user-session-token' } // forwarded to your auth endpoint
47
56
  });
48
57
 
49
- const privateChannel = await client.subscribe('private-chat');
58
+ const channel = await client.subscribe('private-chat');
50
59
  ```
51
60
 
52
- ### Subscribe to Presence Channels
61
+ ### Presence channels
53
62
 
54
63
  ```javascript
55
- const presenceChannel = await client.subscribe('presence-lobby');
56
-
57
- presenceChannel.bind('user-joined', (data) => {
58
- console.log('User joined:', data.user_info);
64
+ const client = new Pingerchips('your_app_key', {
65
+ authEndpoint: 'https://your-server.com/auth',
66
+ authInfo: { token: 'user-session-token' }
59
67
  });
60
- ```
61
-
62
- ## Configuration Options
63
68
 
64
- | Option | Type | Description |
65
- |--------|------|-------------|
66
- | `endpoint` | string | WebSocket endpoint URL |
67
- | `authEndpoint` | string | Your server's auth endpoint for private/presence channels |
68
- | `authInfo` | object | User authentication info passed to your auth endpoint |
69
- | `authHeaders` | object | Additional headers for auth requests |
69
+ const channel = await client.subscribe('presence-lobby');
70
70
 
71
- ## Authentication
72
-
73
- For private and presence channels, you must implement an auth endpoint on your server. See [pingerchips-js-server](https://www.npmjs.com/package/pingerchips-js-server) for server-side implementation.
74
-
75
- ## Channel Methods
76
-
77
- ### `bind(event, callback)`
78
-
79
- Listen for events on a channel.
80
-
81
- ```javascript
82
- channel.bind('message', (data) => {
83
- console.log(data);
71
+ channel.bind('presence_diff', ({ joins, leaves }) => {
72
+ console.log('Joined:', joins);
73
+ console.log('Left:', leaves);
84
74
  });
85
75
  ```
86
76
 
87
- ### `trigger(event, data)`
77
+ ### Reconnection
78
+
79
+ The client automatically reconnects on disconnect. All previously subscribed channels are re-joined transparently — no extra handling needed.
88
80
 
89
- Send an event to a channel.
81
+ ### Socket ID
90
82
 
91
83
  ```javascript
92
- channel.trigger('message', { text: 'Hello!' });
84
+ // Available after first channel join
85
+ const socketId = client.getSocketId();
93
86
  ```
94
87
 
95
- ### `unbind(event)`
96
-
97
- Stop listening for an event.
88
+ ### Unsubscribe
98
89
 
99
90
  ```javascript
100
- channel.unbind('message');
91
+ client.unsubscribe('lobby');
101
92
  ```
102
93
 
94
+ ## Configuration
95
+
96
+ | Option | Type | Default | Description |
97
+ |---|---|---|---|
98
+ | `endpoint` | string | auto | WebSocket endpoint. Defaults to `wss://queue.pingerchips.com/socket` (prod) or `ws://localhost:4000/socket` (dev) |
99
+ | `debug` | string | — | Set to `"false"` to use production endpoint without providing `endpoint` explicitly |
100
+ | `authEndpoint` | string | — | Your server's auth URL for private/presence channels |
101
+ | `authInfo` | object | `{}` | Forwarded to your auth endpoint in the request body |
102
+ | `authHeaders` | object | — | Extra headers added to auth requests |
103
+ | `params` | object | — | Extra params sent with the WebSocket connection |
104
+
105
+ ## Channel API
106
+
107
+ | Method | Description |
108
+ |---|---|
109
+ | `bind(event, callback)` | Listen for an event |
110
+ | `unbind(event)` | Stop listening for an event |
111
+ | `trigger(event, data)` | Send a client event |
112
+ | `leave()` | Leave the channel |
113
+
103
114
  ## License
104
115
 
105
116
  MIT
package/index.js CHANGED
@@ -7,87 +7,124 @@ export class Pingerchips {
7
7
  this.socket = null;
8
8
  this.channels = {};
9
9
  this.socketId = null;
10
- this.authInfo = options.authInfo || {}; // Store auth_info from constructor
10
+ this.authInfo = options.authInfo || {};
11
+ this._endpoint = this._resolveEndpoint();
11
12
 
12
13
  this.connect();
13
14
  }
14
15
 
16
+ _resolveEndpoint() {
17
+ return (
18
+ this.options.endpoint ||
19
+ (this.options.debug === "false"
20
+ ? "wss://queue.pingerchips.com/socket"
21
+ : "ws://localhost:4000/socket")
22
+ );
23
+ }
24
+
15
25
  connect() {
16
26
  const params = { app_key: this.key, ...this.options.params };
17
- const endpoint =
18
- this.options.endpoint || this.options.debug === "false"
19
- ? "wss://pinger-processor.pingerchips.com/socket"
20
- : "ws://localhost:4000/socket";
21
27
 
22
- this.socket = new Socket(endpoint, { params });
28
+ this.socket = new Socket(this._endpoint, { params });
23
29
  this.socket.connect();
24
30
 
25
31
  this.socket.onOpen(() => {
26
- console.log("Pingerchips connected");
27
- // Store socket ID when connection is established
28
- // Phoenix socket uses a ref-based ID system
29
- this.socketId = this.socket.connectionState();
32
+ // Socket ID comes from the server via socket params on open —
33
+ // read it directly from the socket transport if available,
34
+ // otherwise it is set when the first channel join receives socket_id.
35
+ const transportParams = this.socket.params();
36
+ if (transportParams?.socket_id) {
37
+ this.socketId = transportParams.socket_id;
38
+ }
30
39
  });
31
40
 
32
41
  this.socket.onClose(() => {
33
- console.log("Pingerchips disconnected");
42
+ // Clear socket ID on disconnect — will be re-acquired on reconnect
43
+ this.socketId = null;
44
+ this._resubscribeOnReconnect();
34
45
  });
35
46
  }
36
47
 
37
- async subscribe(channelName) {
48
+ // Re-join all previously subscribed channels after reconnect.
49
+ // Phoenix Socket handles reconnect automatically; we re-subscribe channels
50
+ // once the socket is open again.
51
+ _resubscribeOnReconnect() {
52
+ const channelNames = Object.keys(this.channels);
53
+ if (channelNames.length === 0) return;
54
+
55
+ // Clear stale channel references — they are bound to the old socket connection
56
+ this.channels = {};
57
+
58
+ const attemptResubscribe = () => {
59
+ if (this.socket.isConnected()) {
60
+ channelNames.forEach((name) => {
61
+ this.subscribe(name).catch(() => {
62
+ // Silently retry on next reconnect cycle
63
+ });
64
+ });
65
+ } else {
66
+ // Wait for next open event
67
+ this.socket.onOpen(() => {
68
+ channelNames.forEach((name) => {
69
+ this.subscribe(name).catch(() => {});
70
+ });
71
+ });
72
+ }
73
+ };
74
+
75
+ attemptResubscribe();
76
+ }
77
+
78
+ async subscribe(channelName, options = {}) {
38
79
  if (this.channels[channelName]) {
39
80
  return this.channels[channelName];
40
81
  }
41
82
 
42
- // Topic format: "app:{app_key}:room:{channel_name}"
43
83
  const topic = `app:${this.key}:room:${channelName}`;
44
-
45
- // Check if authentication is needed
46
84
  const isPrivate = channelName.startsWith("private-");
47
85
  const isPresence = channelName.startsWith("presence-");
48
86
 
49
87
  let joinParams = {};
50
88
 
51
89
  if (isPrivate || isPresence) {
52
- // Need to authenticate via user's auth endpoint
53
90
  if (!this.options.authEndpoint) {
54
91
  throw new Error(
55
- "authEndpoint must be configured for private/presence channels"
92
+ "authEndpoint must be configured for private/presence channels",
56
93
  );
57
94
  }
58
95
 
59
- try {
60
- const authData = await this.authenticate(channelName);
61
- joinParams = authData;
62
- } catch (error) {
63
- console.error(`Failed to authenticate for ${channelName}:`, error);
64
- throw error;
96
+ if (!this.socketId) {
97
+ throw new Error(
98
+ "Socket ID not yet available. Ensure socket is connected before subscribing to private/presence channels.",
99
+ );
65
100
  }
101
+
102
+ joinParams = await this.authenticate(channelName);
66
103
  }
67
104
 
68
105
  const channel = this.socket.channel(topic, joinParams);
69
106
 
70
- channel
71
- .join()
72
- .receive("ok", (resp) => {
73
- console.log(`Joined ${channelName} successfully`, resp);
74
- })
75
- .receive("error", (resp) => {
76
- console.log(`Unable to join ${channelName}`, resp);
77
- });
78
-
79
- const wrapper = new ChannelWrapper(channel);
80
- this.channels[channelName] = wrapper;
81
- return wrapper;
107
+ return new Promise((resolve, reject) => {
108
+ channel
109
+ .join()
110
+ .receive("ok", (resp) => {
111
+ if (resp.socket_id && !this.socketId) {
112
+ this.socketId = resp.socket_id;
113
+ }
114
+
115
+ const wrapper = new ChannelWrapper(channel);
116
+ this.channels[channelName] = wrapper;
117
+ resolve(wrapper);
118
+ })
119
+ .receive("error", (resp) => {
120
+ reject(new Error(`Failed to join channel: ${JSON.stringify(resp)}`));
121
+ });
122
+ });
82
123
  }
83
124
 
84
125
  async authenticate(channelName) {
85
- // Call user's auth endpoint with socket_id, channel_name, and auth_info
86
- // User's server will use Pingerchips.authenticate() to sign the data
87
- const socketId = this.getSocketId();
88
-
89
126
  const body = {
90
- socket_id: socketId,
127
+ socket_id: this.socketId,
91
128
  channel_name: channelName,
92
129
  auth_info: this.authInfo,
93
130
  };
@@ -108,26 +145,20 @@ export class Pingerchips {
108
145
  throw new Error(error.error || "Authentication failed");
109
146
  }
110
147
 
111
- // Expected response from user's server: { auth: "app_key:signature", channel_data: "..." }
112
- return await response.json();
148
+ return response.json();
113
149
  }
114
150
 
115
151
  getSocketId() {
116
- // Phoenix socket generates a unique ref for each connection
117
- // We can use the socket's internal state or generate a compatible ID
118
- // For now, use a simple approach: use the socket's makeRef() output
119
152
  if (!this.socketId) {
120
- this.socketId = `socket-${Date.now()}-${Math.random()
121
- .toString(36)
122
- .substring(2, 11)}`;
153
+ throw new Error(
154
+ "Socket ID not available. Connect and join a channel first.",
155
+ );
123
156
  }
124
157
  return this.socketId;
125
158
  }
126
159
 
127
160
  getHttpEndpoint() {
128
- const wsEndpoint = this.options.endpoint || "ws://localhost:4000/socket";
129
- // Convert ws:// to http:// and wss:// to https://
130
- return wsEndpoint
161
+ return this._endpoint
131
162
  .replace("ws://", "http://")
132
163
  .replace("wss://", "https://")
133
164
  .replace("/socket", "");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pingerchips-js",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "Pingerchips JavaScript client SDK for real-time WebSocket connections",
5
5
  "main": "index.js",
6
6
  "type": "module",