pingerchips-js 2.0.0 → 2.1.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.
Files changed (2) hide show
  1. package/index.js +82 -51
  2. package/package.json +1 -1
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.0",
4
4
  "description": "Pingerchips JavaScript client SDK for real-time WebSocket connections",
5
5
  "main": "index.js",
6
6
  "type": "module",