pingerchips-js-server 2.0.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 (3) hide show
  1. package/README.md +124 -0
  2. package/index.js +167 -0
  3. package/package.json +34 -0
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # Pingerchips Server SDK
2
+
3
+ Server-side SDK for triggering events and authenticating users with Pingerchips.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install pingerchips-js-server
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Initialize
14
+
15
+ ```javascript
16
+ import PingerchipsServer from 'pingerchips-js-server';
17
+
18
+ const pingerchips = new PingerchipsServer('app_id', 'app_secret', {
19
+ appKey: 'app_key',
20
+ endpoint: 'https://pinger-processor.pingerchips.com/api'
21
+ });
22
+ ```
23
+
24
+ ### Trigger Events
25
+
26
+ Send events to channels from your server:
27
+
28
+ ```javascript
29
+ await pingerchips.trigger('lobby', 'message', {
30
+ text: 'Hello from server!',
31
+ timestamp: Date.now()
32
+ });
33
+ ```
34
+
35
+ ### Authenticate Users (Private/Presence Channels)
36
+
37
+ Implement an auth endpoint on your server:
38
+
39
+ ```javascript
40
+ import express from 'express';
41
+
42
+ const app = express();
43
+ app.use(express.json());
44
+
45
+ app.post('/auth', (req, res) => {
46
+ const { socket_id, channel_name, auth_info } = req.body;
47
+
48
+ // Validate user from session/token
49
+ const user = validateUser(auth_info);
50
+ if (!user) {
51
+ return res.status(403).json({ error: 'Unauthorized' });
52
+ }
53
+
54
+ // For presence channels, provide user data
55
+ const userData = channel_name.startsWith('presence-') ? {
56
+ user_id: user.id,
57
+ user_info: {
58
+ name: user.name,
59
+ avatar: user.avatar
60
+ }
61
+ } : null;
62
+
63
+ // Sign authentication with Pingerchips
64
+ const authData = pingerchips.authenticate(socket_id, channel_name, userData);
65
+ res.json(authData);
66
+ });
67
+ ```
68
+
69
+ ## API Reference
70
+
71
+ ### `new PingerchipsServer(appId, appSecret, options)`
72
+
73
+ Create a new server instance.
74
+
75
+ **Options:**
76
+ - `appKey` - Your app key (required for authentication)
77
+ - `endpoint` - API endpoint URL
78
+ - `token` - API token for internal endpoints
79
+ - `mtls` - mTLS configuration for secure connections
80
+
81
+ ### `trigger(channel, event, data)`
82
+
83
+ Send an event to a channel.
84
+
85
+ ```javascript
86
+ await pingerchips.trigger('my-channel', 'my-event', { message: 'Hello' });
87
+ ```
88
+
89
+ ### `authenticate(socketId, channelName, userData?)`
90
+
91
+ Generate signed authentication for private/presence channels.
92
+
93
+ **Parameters:**
94
+ - `socketId` - Socket ID from client
95
+ - `channelName` - Channel name (e.g., "private-chat" or "presence-lobby")
96
+ - `userData` - User data for presence channels (must include `user_id`)
97
+
98
+ **Returns:**
99
+ ```javascript
100
+ {
101
+ auth: "app_key:hmac_signature",
102
+ channel_data: "{\"user_id\":\"123\",...}" // presence channels only
103
+ }
104
+ ```
105
+
106
+ ## mTLS Support
107
+
108
+ For secure server-to-server communication:
109
+
110
+ ```javascript
111
+ const pingerchips = new PingerchipsServer('app_id', 'app_secret', {
112
+ endpoint: 'https://pinger-processor.pingerchips.com/api',
113
+ mtls: {
114
+ enabled: true,
115
+ cert: '/path/to/client-cert.pem',
116
+ key: '/path/to/client-key.pem',
117
+ ca: '/path/to/ca-cert.pem'
118
+ }
119
+ });
120
+ ```
121
+
122
+ ## License
123
+
124
+ MIT
package/index.js ADDED
@@ -0,0 +1,167 @@
1
+ import fs from "fs";
2
+ import https from "https";
3
+ import crypto from "crypto";
4
+
5
+ class PingerchipsServer {
6
+ constructor(appId, appSecret, options = {}) {
7
+ this.appId = appId;
8
+ this.appSecret = appSecret;
9
+ this.appKey = options.appKey || appId; // App key for signing
10
+ this.endpoint =
11
+ options.endpoint ||
12
+ process.env.PINGERCHIPS_API_ENDPOINT ||
13
+ process.env.NODE_ENV === "production"
14
+ ? "https://pinger-processor.pingerchips.com"
15
+ : "http://localhost:4000";
16
+ this.token = options.token;
17
+
18
+ // mTLS configuration
19
+ this.mtls = {
20
+ enabled: options.mtls?.enabled || false,
21
+ cert: options.mtls?.cert,
22
+ key: options.mtls?.key,
23
+ ca: options.mtls?.ca,
24
+ rejectUnauthorized: options.mtls?.rejectUnauthorized !== false,
25
+ };
26
+
27
+ this.fetchPromise = import("node-fetch").then((mod) => mod.default);
28
+ }
29
+
30
+ _createHttpsAgent() {
31
+ if (!this.mtls.enabled) {
32
+ return undefined;
33
+ }
34
+
35
+ const agentOptions = {
36
+ rejectUnauthorized: this.mtls.rejectUnauthorized,
37
+ };
38
+
39
+ // Load cert, key, and ca - support both file paths and direct content
40
+ if (this.mtls.cert) {
41
+ agentOptions.cert = this._loadCertificate(this.mtls.cert);
42
+ }
43
+ if (this.mtls.key) {
44
+ agentOptions.key = this._loadCertificate(this.mtls.key);
45
+ }
46
+ if (this.mtls.ca) {
47
+ agentOptions.ca = this._loadCertificate(this.mtls.ca);
48
+ }
49
+
50
+ return new https.Agent(agentOptions);
51
+ }
52
+
53
+ _loadCertificate(certOrPath) {
54
+ // If it looks like a certificate/key content (starts with -----), return as-is
55
+ if (
56
+ typeof certOrPath === "string" &&
57
+ certOrPath.trim().startsWith("-----")
58
+ ) {
59
+ return certOrPath;
60
+ }
61
+ // Otherwise, treat as file path
62
+ return fs.readFileSync(certOrPath, "utf8");
63
+ }
64
+
65
+ async trigger(channel, event, data) {
66
+ const fetch = await this.fetchPromise;
67
+ const url = `${this.endpoint}/apps/${this.appId}/trigger`;
68
+
69
+ const requestOptions = {
70
+ method: "POST",
71
+ headers: {
72
+ "Content-Type": "application/json",
73
+ token: this.token,
74
+ },
75
+ body: JSON.stringify({
76
+ app_id: this.appId,
77
+ app_secret: this.appSecret,
78
+ channel,
79
+ event,
80
+ data,
81
+ }),
82
+ };
83
+
84
+ // Add HTTPS agent if mTLS is enabled
85
+ if (this.mtls.enabled) {
86
+ requestOptions.agent = this._createHttpsAgent();
87
+ }
88
+
89
+ const response = await fetch(url, requestOptions);
90
+
91
+ if (!response.ok) {
92
+ const error = await response.json().catch(() => ({}));
93
+ throw new Error(
94
+ `Failed to trigger event: ${response.statusText} - ${error.error || ""}`
95
+ );
96
+ }
97
+
98
+ return await response.json();
99
+ }
100
+
101
+ /**
102
+ * Authenticate a user for a private or presence channel
103
+ *
104
+ * This method is called by your auth endpoint to sign user data.
105
+ * The signature proves that your server authorized the user to join the channel.
106
+ *
107
+ * @param {string} socketId - Socket ID from the client SDK
108
+ * @param {string} channelName - Channel name (e.g., "private-chat" or "presence-lobby")
109
+ * @param {object} userData - User data for presence channels (must include user_id)
110
+ * @returns {object} - Signed authentication data: { auth, channel_data? }
111
+ *
112
+ * @example
113
+ * // In your Express.js auth endpoint:
114
+ * app.post('/auth', (req, res) => {
115
+ * const { socket_id, channel_name, auth_info } = req.body;
116
+ *
117
+ * // Validate user from session/token
118
+ * const user = req.session.user;
119
+ * if (!user) return res.status(403).json({ error: 'Unauthorized' });
120
+ *
121
+ * // For presence channels, provide user data
122
+ * const userData = channel_name.startsWith('presence-') ? {
123
+ * user_id: user.id,
124
+ * user_info: { name: user.name, avatar: user.avatar }
125
+ * } : null;
126
+ *
127
+ * const authData = pingerchips.authenticate(socket_id, channel_name, userData);
128
+ * res.json(authData);
129
+ * });
130
+ */
131
+ authenticate(socketId, channelName, userData = null) {
132
+ // Build the full topic name
133
+ const fullTopic = `app:${this.appKey}:room:${channelName}`;
134
+
135
+ // Build string to sign based on channel type
136
+ let stringToSign;
137
+ let channelData = null;
138
+
139
+ if (userData && channelName.startsWith("presence-")) {
140
+ // Presence channel: include user data in signature
141
+ channelData = JSON.stringify(userData);
142
+ stringToSign = `${socketId}:${fullTopic}:${channelData}`;
143
+ } else {
144
+ // Private channel: just socket_id and topic
145
+ stringToSign = `${socketId}:${fullTopic}`;
146
+ }
147
+
148
+ // Generate HMAC-SHA256 signature using app_secret
149
+ const signature = crypto
150
+ .createHmac("sha256", this.appSecret)
151
+ .update(stringToSign)
152
+ .digest("hex");
153
+
154
+ const auth = `${this.appKey}:${signature}`;
155
+
156
+ // Build response
157
+ const response = { auth };
158
+
159
+ if (channelData) {
160
+ response.channel_data = channelData;
161
+ }
162
+
163
+ return response;
164
+ }
165
+ }
166
+
167
+ export default PingerchipsServer;
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "pingerchips-js-server",
3
+ "version": "2.0.0",
4
+ "description": "Pingerchips server SDK for Node.js - trigger events and authenticate users",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "test": "echo \"Error: no test specified\" && exit 1"
9
+ },
10
+ "keywords": [
11
+ "pingerchips",
12
+ "websocket",
13
+ "realtime",
14
+ "server",
15
+ "backend",
16
+ "pusher"
17
+ ],
18
+ "author": "Pingerchips",
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "node-fetch": "^3.3.0"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/pingerchips/pingerchips-js-server"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/pingerchips/pingerchips-js-server/issues"
29
+ },
30
+ "homepage": "https://pingerchips.com",
31
+ "engines": {
32
+ "node": ">=14.0.0"
33
+ }
34
+ }