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.
- package/index.js +82 -51
- 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 || {};
|
|
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(
|
|
28
|
+
this.socket = new Socket(this._endpoint, { params });
|
|
23
29
|
this.socket.connect();
|
|
24
30
|
|
|
25
31
|
this.socket.onOpen(() => {
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
.
|
|
122
|
-
|
|
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
|
-
|
|
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", "");
|