@ceylonitsolutions/pushstream-js 1.0.0 → 1.0.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.
- package/README.md +149 -1
- package/dist/pushstream.min.js +225 -0
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -1 +1,149 @@
|
|
|
1
|
-
#
|
|
1
|
+
# PushStream JavaScript SDK
|
|
2
|
+
|
|
3
|
+
Real-time messaging SDK for JavaScript/Node.js applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @ceylonitsolutions/pushstream-js
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
### Browser
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<script src="pushstream.js"></script>
|
|
17
|
+
<script>
|
|
18
|
+
const client = new PushStream('your-app-key');
|
|
19
|
+
|
|
20
|
+
client.connect().then(socketId => {
|
|
21
|
+
console.log('Connected:', socketId);
|
|
22
|
+
|
|
23
|
+
const channel = client.subscribe('my-channel');
|
|
24
|
+
channel.bind('my-event', (data) => {
|
|
25
|
+
console.log('Received:', data);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
</script>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Node.js
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
const PushStream = require('@ceylonitsolutions/pushstream-js');
|
|
35
|
+
|
|
36
|
+
const client = new PushStream('your-app-key');
|
|
37
|
+
|
|
38
|
+
client.connect().then(socketId => {
|
|
39
|
+
console.log('Connected:', socketId);
|
|
40
|
+
|
|
41
|
+
const channel = client.subscribe('my-channel');
|
|
42
|
+
channel.bind('my-event', (data) => {
|
|
43
|
+
console.log('Received:', data);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
const client = new PushStream('your-app-key', {
|
|
52
|
+
wsUrl: 'wss://ws.pushstream.ceylonitsolutions.online',
|
|
53
|
+
apiUrl: 'https://api.pushstream.ceylonitsolutions.online'
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## API Reference
|
|
58
|
+
|
|
59
|
+
### Client Methods
|
|
60
|
+
|
|
61
|
+
#### `connect()`
|
|
62
|
+
Establishes WebSocket connection.
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
client.connect()
|
|
66
|
+
.then(socketId => console.log('Connected'))
|
|
67
|
+
.catch(error => console.error('Failed to connect'));
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### `subscribe(channelName)`
|
|
71
|
+
Subscribe to a channel.
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
const channel = client.subscribe('my-channel');
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### `unsubscribe(channelName)`
|
|
78
|
+
Unsubscribe from a channel.
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
client.unsubscribe('my-channel');
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### `disconnect()`
|
|
85
|
+
Close the connection.
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
client.disconnect();
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### `publish(appId, appSecret, channel, event, data)`
|
|
92
|
+
Publish events via REST API.
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
await client.publish(
|
|
96
|
+
'app-id',
|
|
97
|
+
'app-secret',
|
|
98
|
+
'my-channel',
|
|
99
|
+
'my-event',
|
|
100
|
+
{ message: 'Hello' }
|
|
101
|
+
);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Channel Methods
|
|
105
|
+
|
|
106
|
+
#### `bind(event, callback)`
|
|
107
|
+
Listen for events on the channel.
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
channel.bind('my-event', (data) => {
|
|
111
|
+
console.log(data);
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### `unbind(event, callback)`
|
|
116
|
+
Remove event listener.
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
channel.unbind('my-event', callback);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### `unsubscribe()`
|
|
123
|
+
Unsubscribe from the channel.
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
channel.unsubscribe();
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Features
|
|
130
|
+
|
|
131
|
+
- WebSocket real-time messaging
|
|
132
|
+
- Automatic reconnection with exponential backoff
|
|
133
|
+
- Channel subscriptions
|
|
134
|
+
- Event binding
|
|
135
|
+
- REST API publishing
|
|
136
|
+
- Browser and Node.js support
|
|
137
|
+
|
|
138
|
+
## Requirements
|
|
139
|
+
|
|
140
|
+
- Node.js >= 14.0.0 (for Node.js usage)
|
|
141
|
+
- Modern browser with WebSocket support
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT
|
|
146
|
+
|
|
147
|
+
## Author
|
|
148
|
+
|
|
149
|
+
Ceylon IT Solutions
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
class PushStream {
|
|
2
|
+
constructor(appKey, options = {}) {
|
|
3
|
+
this.appKey = appKey;
|
|
4
|
+
this.wsUrl = options.wsUrl || 'wss://ws.pushstream.ceylonitsolutions.online';
|
|
5
|
+
this.apiUrl = options.apiUrl || 'https://api.pushstream.ceylonitsolutions.online';
|
|
6
|
+
this.ws = null;
|
|
7
|
+
this.socketId = null;
|
|
8
|
+
this.channels = new Map();
|
|
9
|
+
this.reconnectAttempts = 0;
|
|
10
|
+
this.maxReconnectAttempts = 5;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
connect() {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
try {
|
|
16
|
+
this.ws = new WebSocket(this.wsUrl);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
reject(error);
|
|
19
|
+
this.attemptReconnect();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const timeout = setTimeout(() => {
|
|
24
|
+
if (!this.socketId) {
|
|
25
|
+
reject(new Error('Connection timeout'));
|
|
26
|
+
this.attemptReconnect();
|
|
27
|
+
}
|
|
28
|
+
}, 10000);
|
|
29
|
+
|
|
30
|
+
this.ws.onopen = () => {
|
|
31
|
+
console.log('[PushStream] Connected');
|
|
32
|
+
this.reconnectAttempts = 0;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
this.ws.onmessage = (event) => {
|
|
36
|
+
const message = JSON.parse(event.data);
|
|
37
|
+
|
|
38
|
+
if (message.event === 'pusher:connection_established') {
|
|
39
|
+
const data = JSON.parse(message.data);
|
|
40
|
+
this.socketId = data.socket_id;
|
|
41
|
+
clearTimeout(timeout);
|
|
42
|
+
resolve(this.socketId);
|
|
43
|
+
} else if (message.event === 'pusher:error') {
|
|
44
|
+
console.error('[PushStream] Error:', message.data);
|
|
45
|
+
} else {
|
|
46
|
+
this.handleMessage(message);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
this.ws.onclose = () => {
|
|
51
|
+
console.log('[PushStream] Disconnected');
|
|
52
|
+
clearTimeout(timeout);
|
|
53
|
+
this.socketId = null;
|
|
54
|
+
this.attemptReconnect();
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
this.ws.onerror = (error) => {
|
|
58
|
+
console.error('[PushStream] WebSocket error:', error);
|
|
59
|
+
clearTimeout(timeout);
|
|
60
|
+
if (!this.socketId) reject(error);
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
attemptReconnect() {
|
|
66
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
67
|
+
console.error('[PushStream] Max reconnection attempts reached');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
|
|
72
|
+
this.reconnectAttempts++;
|
|
73
|
+
|
|
74
|
+
console.log(`[PushStream] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
|
|
75
|
+
setTimeout(() => this.connect(), delay);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
subscribe(channelName, callbacks = {}) {
|
|
79
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
80
|
+
throw new Error('Not connected');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const channel = new Channel(channelName, this, callbacks);
|
|
84
|
+
this.channels.set(channelName, channel);
|
|
85
|
+
|
|
86
|
+
this.send({
|
|
87
|
+
event: 'pusher:subscribe',
|
|
88
|
+
data: { channel: channelName }
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return channel;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
unsubscribe(channelName) {
|
|
95
|
+
this.send({
|
|
96
|
+
event: 'pusher:unsubscribe',
|
|
97
|
+
data: { channel: channelName }
|
|
98
|
+
});
|
|
99
|
+
this.channels.delete(channelName);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
send(data) {
|
|
103
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
104
|
+
this.ws.send(JSON.stringify(data));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
handleMessage(message) {
|
|
109
|
+
const channel = this.channels.get(message.channel);
|
|
110
|
+
if (channel) {
|
|
111
|
+
channel.handleEvent(message.event, JSON.parse(message.data));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
disconnect() {
|
|
116
|
+
if (this.ws) {
|
|
117
|
+
this.ws.close();
|
|
118
|
+
this.ws = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// REST API methods
|
|
123
|
+
async publish(appId, appSecret, channel, event, data) {
|
|
124
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
125
|
+
const body = JSON.stringify({ name: event, channel, data });
|
|
126
|
+
const path = `/api/apps/${appId}/events`;
|
|
127
|
+
const queryString = `auth_timestamp=${timestamp}`;
|
|
128
|
+
const stringToSign = `POST\n${path}\n${queryString}\n${body}`;
|
|
129
|
+
|
|
130
|
+
const signature = await this.hmacSha256(stringToSign, appSecret);
|
|
131
|
+
const authHeader = `${appId}:${signature}`;
|
|
132
|
+
|
|
133
|
+
const response = await fetch(`${this.apiUrl}${path}?${queryString}`, {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: {
|
|
136
|
+
'Authorization': authHeader,
|
|
137
|
+
'Content-Type': 'application/json'
|
|
138
|
+
},
|
|
139
|
+
body
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return response.json();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async hmacSha256(message, secret) {
|
|
150
|
+
// Node.js
|
|
151
|
+
if (typeof window === 'undefined') {
|
|
152
|
+
const crypto = require('crypto');
|
|
153
|
+
return crypto.createHmac('sha256', secret).update(message).digest('hex');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Browser
|
|
157
|
+
const encoder = new TextEncoder();
|
|
158
|
+
const keyData = encoder.encode(secret);
|
|
159
|
+
const messageData = encoder.encode(message);
|
|
160
|
+
|
|
161
|
+
const key = await crypto.subtle.importKey(
|
|
162
|
+
'raw',
|
|
163
|
+
keyData,
|
|
164
|
+
{ name: 'HMAC', hash: 'SHA-256' },
|
|
165
|
+
false,
|
|
166
|
+
['sign']
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const signature = await crypto.subtle.sign('HMAC', key, messageData);
|
|
170
|
+
return Array.from(new Uint8Array(signature))
|
|
171
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
172
|
+
.join('');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
class Channel {
|
|
177
|
+
constructor(name, client, callbacks = {}) {
|
|
178
|
+
this.name = name;
|
|
179
|
+
this.client = client;
|
|
180
|
+
this.callbacks = callbacks;
|
|
181
|
+
this.eventHandlers = new Map();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
bind(event, callback) {
|
|
185
|
+
if (!this.eventHandlers.has(event)) {
|
|
186
|
+
this.eventHandlers.set(event, []);
|
|
187
|
+
}
|
|
188
|
+
this.eventHandlers.get(event).push(callback);
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
unbind(event, callback) {
|
|
193
|
+
if (!this.eventHandlers.has(event)) return;
|
|
194
|
+
|
|
195
|
+
if (callback) {
|
|
196
|
+
const handlers = this.eventHandlers.get(event);
|
|
197
|
+
const index = handlers.indexOf(callback);
|
|
198
|
+
if (index > -1) handlers.splice(index, 1);
|
|
199
|
+
} else {
|
|
200
|
+
this.eventHandlers.delete(event);
|
|
201
|
+
}
|
|
202
|
+
return this;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
handleEvent(event, data) {
|
|
206
|
+
const handlers = this.eventHandlers.get(event);
|
|
207
|
+
if (handlers) {
|
|
208
|
+
handlers.forEach(handler => handler(data));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
unsubscribe() {
|
|
213
|
+
this.client.unsubscribe(this.name);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Node.js support
|
|
218
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
219
|
+
module.exports = PushStream;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Browser support
|
|
223
|
+
if (typeof window !== 'undefined') {
|
|
224
|
+
window.PushStream = PushStream;
|
|
225
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ceylonitsolutions/pushstream-js",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "PushStream JavaScript SDK for real-time messaging",
|
|
5
5
|
"main": "pushstream.js",
|
|
6
|
+
"browser": "dist/pushstream.min.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"pushstream.js",
|
|
9
|
+
"dist/pushstream.min.js"
|
|
10
|
+
],
|
|
6
11
|
"scripts": {
|
|
7
12
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
13
|
},
|