velora-node-sdk 1.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.
- package/LICENSE +6 -0
- package/README.md +503 -0
- package/dist/auth/CredentialManager.d.ts +17 -0
- package/dist/auth/CredentialManager.js +116 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +5 -0
- package/dist/client/VeloraClient.d.ts +32 -0
- package/dist/client/VeloraClient.js +124 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +5 -0
- package/dist/http/HttpClient.d.ts +22 -0
- package/dist/http/HttpClient.js +116 -0
- package/dist/http/RetryManager.d.ts +9 -0
- package/dist/http/RetryManager.js +43 -0
- package/dist/http/index.d.ts +2 -0
- package/dist/http/index.js +7 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +14 -0
- package/dist/types/api.d.ts +128 -0
- package/dist/types/api.js +2 -0
- package/dist/types/client.d.ts +44 -0
- package/dist/types/client.js +2 -0
- package/dist/types/errors.d.ts +30 -0
- package/dist/types/errors.js +105 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +19 -0
- package/dist/ws/MessageQueue.d.ts +8 -0
- package/dist/ws/MessageQueue.js +26 -0
- package/dist/ws/ReconnectManager.d.ts +13 -0
- package/dist/ws/ReconnectManager.js +42 -0
- package/dist/ws/WebSocketClient.d.ts +21 -0
- package/dist/ws/WebSocketClient.js +127 -0
- package/dist/ws/index.d.ts +3 -0
- package/dist/ws/index.js +9 -0
- package/package.json +39 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./api"), exports);
|
|
18
|
+
__exportStar(require("./client"), exports);
|
|
19
|
+
__exportStar(require("./errors"), exports);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MessageQueue = void 0;
|
|
4
|
+
class MessageQueue {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.queue = [];
|
|
7
|
+
}
|
|
8
|
+
enqueue(message) {
|
|
9
|
+
this.queue.push(message);
|
|
10
|
+
}
|
|
11
|
+
flush() {
|
|
12
|
+
const messages = [...this.queue];
|
|
13
|
+
this.queue = [];
|
|
14
|
+
return messages;
|
|
15
|
+
}
|
|
16
|
+
clear() {
|
|
17
|
+
this.queue = [];
|
|
18
|
+
}
|
|
19
|
+
size() {
|
|
20
|
+
return this.queue.length;
|
|
21
|
+
}
|
|
22
|
+
isEmpty() {
|
|
23
|
+
return this.queue.length === 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.MessageQueue = MessageQueue;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare class ReconnectManager {
|
|
2
|
+
private readonly initialBackoffMs;
|
|
3
|
+
private readonly maxBackoffMs;
|
|
4
|
+
private attemptCount;
|
|
5
|
+
private timeoutId;
|
|
6
|
+
constructor(initialBackoffMs?: number, maxBackoffMs?: number);
|
|
7
|
+
getNextBackoffMs(): number;
|
|
8
|
+
nextAttempt(): void;
|
|
9
|
+
resetBackoff(): void;
|
|
10
|
+
scheduleReconnect(callback: () => void): void;
|
|
11
|
+
cancel(): void;
|
|
12
|
+
getAttemptCount(): number;
|
|
13
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ReconnectManager = void 0;
|
|
4
|
+
class ReconnectManager {
|
|
5
|
+
constructor(initialBackoffMs = 1000, maxBackoffMs = 60000) {
|
|
6
|
+
this.attemptCount = 0;
|
|
7
|
+
this.timeoutId = null;
|
|
8
|
+
this.initialBackoffMs = initialBackoffMs;
|
|
9
|
+
this.maxBackoffMs = maxBackoffMs;
|
|
10
|
+
}
|
|
11
|
+
getNextBackoffMs() {
|
|
12
|
+
const exponentialBackoff = this.initialBackoffMs * Math.pow(2, this.attemptCount);
|
|
13
|
+
return Math.min(exponentialBackoff, this.maxBackoffMs);
|
|
14
|
+
}
|
|
15
|
+
nextAttempt() {
|
|
16
|
+
this.attemptCount += 1;
|
|
17
|
+
}
|
|
18
|
+
resetBackoff() {
|
|
19
|
+
this.attemptCount = 0;
|
|
20
|
+
if (this.timeoutId) {
|
|
21
|
+
clearTimeout(this.timeoutId);
|
|
22
|
+
this.timeoutId = null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
scheduleReconnect(callback) {
|
|
26
|
+
const backoffMs = this.getNextBackoffMs();
|
|
27
|
+
this.timeoutId = setTimeout(() => {
|
|
28
|
+
this.nextAttempt();
|
|
29
|
+
callback();
|
|
30
|
+
}, backoffMs);
|
|
31
|
+
}
|
|
32
|
+
cancel() {
|
|
33
|
+
if (this.timeoutId) {
|
|
34
|
+
clearTimeout(this.timeoutId);
|
|
35
|
+
this.timeoutId = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
getAttemptCount() {
|
|
39
|
+
return this.attemptCount;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.ReconnectManager = ReconnectManager;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { CredentialManager } from '../auth/CredentialManager';
|
|
3
|
+
export declare class WebSocketClient extends EventEmitter {
|
|
4
|
+
private ws;
|
|
5
|
+
private baseUrl;
|
|
6
|
+
private credentialManager;
|
|
7
|
+
private reconnectManager;
|
|
8
|
+
private messageQueue;
|
|
9
|
+
private isIntentionallyClosed;
|
|
10
|
+
private isConnected;
|
|
11
|
+
private connectionAttempts;
|
|
12
|
+
constructor(baseUrl: string, credentialManager: CredentialManager, initialBackoffMs?: number, maxBackoffMs?: number);
|
|
13
|
+
connect(): Promise<void>;
|
|
14
|
+
disconnect(): void;
|
|
15
|
+
isWSConnected(): boolean;
|
|
16
|
+
send(message: Record<string, unknown>): void;
|
|
17
|
+
private handleOpen;
|
|
18
|
+
private handleMessage;
|
|
19
|
+
private handleClose;
|
|
20
|
+
private handleError;
|
|
21
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebSocketClient = void 0;
|
|
4
|
+
const events_1 = require("events");
|
|
5
|
+
const errors_1 = require("../types/errors");
|
|
6
|
+
const ReconnectManager_1 = require("./ReconnectManager");
|
|
7
|
+
const MessageQueue_1 = require("./MessageQueue");
|
|
8
|
+
class WebSocketClient extends events_1.EventEmitter {
|
|
9
|
+
constructor(baseUrl, credentialManager, initialBackoffMs = 1000, maxBackoffMs = 60000) {
|
|
10
|
+
super();
|
|
11
|
+
this.ws = null;
|
|
12
|
+
this.isIntentionallyClosed = false;
|
|
13
|
+
this.isConnected = false;
|
|
14
|
+
this.connectionAttempts = 0;
|
|
15
|
+
this.baseUrl = baseUrl;
|
|
16
|
+
this.credentialManager = credentialManager;
|
|
17
|
+
this.reconnectManager = new ReconnectManager_1.ReconnectManager(initialBackoffMs, maxBackoffMs);
|
|
18
|
+
this.messageQueue = new MessageQueue_1.MessageQueue();
|
|
19
|
+
}
|
|
20
|
+
async connect() {
|
|
21
|
+
if (this.ws !== null) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const token = this.credentialManager.getToken();
|
|
25
|
+
if (!token) {
|
|
26
|
+
throw new errors_1.AuthError('No valid token for WebSocket connection');
|
|
27
|
+
}
|
|
28
|
+
if (!this.credentialManager.hasPermission('read')) {
|
|
29
|
+
throw new errors_1.AuthError('Missing read permission for WebSocket');
|
|
30
|
+
}
|
|
31
|
+
this.isIntentionallyClosed = false;
|
|
32
|
+
this.connectionAttempts += 1;
|
|
33
|
+
try {
|
|
34
|
+
const url = `${this.baseUrl}/ws?access_token=${encodeURIComponent(token)}`;
|
|
35
|
+
this.emit('connecting');
|
|
36
|
+
this.ws = new WebSocket(url);
|
|
37
|
+
this.ws.addEventListener('open', () => this.handleOpen());
|
|
38
|
+
this.ws.addEventListener('message', (event) => this.handleMessage(event));
|
|
39
|
+
this.ws.addEventListener('close', () => this.handleClose());
|
|
40
|
+
this.ws.addEventListener('error', (error) => this.handleError(error));
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
this.ws = null;
|
|
44
|
+
throw new errors_1.NetworkError(error instanceof Error ? error.message : 'WebSocket connection failed');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
disconnect() {
|
|
48
|
+
this.isIntentionallyClosed = true;
|
|
49
|
+
this.reconnectManager.cancel();
|
|
50
|
+
if (this.ws) {
|
|
51
|
+
this.ws.removeEventListener('open', () => this.handleOpen());
|
|
52
|
+
this.ws.removeEventListener('message', (event) => this.handleMessage(event));
|
|
53
|
+
this.ws.removeEventListener('close', () => this.handleClose());
|
|
54
|
+
this.ws.removeEventListener('error', (error) => this.handleError(error));
|
|
55
|
+
if (this.ws.readyState === 1) {
|
|
56
|
+
this.ws.close();
|
|
57
|
+
}
|
|
58
|
+
this.ws = null;
|
|
59
|
+
}
|
|
60
|
+
this.isConnected = false;
|
|
61
|
+
this.messageQueue.clear();
|
|
62
|
+
this.emit('disconnected');
|
|
63
|
+
}
|
|
64
|
+
isWSConnected() {
|
|
65
|
+
return this.isConnected && this.ws !== null;
|
|
66
|
+
}
|
|
67
|
+
send(message) {
|
|
68
|
+
if (this.isConnected && this.ws) {
|
|
69
|
+
this.ws.send(JSON.stringify(message));
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
this.messageQueue.enqueue(message);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
handleOpen() {
|
|
76
|
+
this.isConnected = true;
|
|
77
|
+
this.reconnectManager.resetBackoff();
|
|
78
|
+
this.connectionAttempts = 0;
|
|
79
|
+
this.emit('connected');
|
|
80
|
+
const queuedMessages = this.messageQueue.flush();
|
|
81
|
+
for (const msg of queuedMessages) {
|
|
82
|
+
if (this.ws) {
|
|
83
|
+
this.ws.send(JSON.stringify(msg));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
handleMessage(event) {
|
|
88
|
+
try {
|
|
89
|
+
const payload = JSON.parse(event.data);
|
|
90
|
+
this.emit('message', payload);
|
|
91
|
+
if (payload.event === 'track_changed') {
|
|
92
|
+
this.emit('track_changed', payload.data);
|
|
93
|
+
}
|
|
94
|
+
else if (payload.event === 'playback_state_changed') {
|
|
95
|
+
this.emit('playback_state_changed', payload.data);
|
|
96
|
+
}
|
|
97
|
+
else if (payload.event === 'connected') {
|
|
98
|
+
this.emit('event:connected');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
this.emit('error', new errors_1.NetworkError('Failed to parse WebSocket message'));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
handleClose() {
|
|
106
|
+
this.isConnected = false;
|
|
107
|
+
if (!this.isIntentionallyClosed) {
|
|
108
|
+
this.reconnectManager.scheduleReconnect(() => {
|
|
109
|
+
void this.connect();
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.emit('disconnected');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
handleError(error) {
|
|
117
|
+
this.isConnected = false;
|
|
118
|
+
const wsError = new errors_1.NetworkError(error instanceof Error ? error.message : 'WebSocket error occurred');
|
|
119
|
+
this.emit('error', wsError);
|
|
120
|
+
if (!this.isIntentionallyClosed) {
|
|
121
|
+
this.reconnectManager.scheduleReconnect(() => {
|
|
122
|
+
void this.connect();
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
exports.WebSocketClient = WebSocketClient;
|
package/dist/ws/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MessageQueue = exports.ReconnectManager = exports.WebSocketClient = void 0;
|
|
4
|
+
var WebSocketClient_1 = require("./WebSocketClient");
|
|
5
|
+
Object.defineProperty(exports, "WebSocketClient", { enumerable: true, get: function () { return WebSocketClient_1.WebSocketClient; } });
|
|
6
|
+
var ReconnectManager_1 = require("./ReconnectManager");
|
|
7
|
+
Object.defineProperty(exports, "ReconnectManager", { enumerable: true, get: function () { return ReconnectManager_1.ReconnectManager; } });
|
|
8
|
+
var MessageQueue_1 = require("./MessageQueue");
|
|
9
|
+
Object.defineProperty(exports, "MessageQueue", { enumerable: true, get: function () { return MessageQueue_1.MessageQueue; } });
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "velora-node-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"author": "CrickDevs",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"description": "Node.js SDK for Velora local API with registration, HTTP endpoints, and WebSocket support",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"type": "commonjs",
|
|
10
|
+
"files": ["dist", "README.md"],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"require": "./dist/index.js",
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"build:watch": "tsc --watch",
|
|
21
|
+
"test": "jest",
|
|
22
|
+
"test:watch": "jest --watch",
|
|
23
|
+
"test:coverage": "jest --coverage"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"velora",
|
|
27
|
+
"sdk",
|
|
28
|
+
"api",
|
|
29
|
+
"local-api",
|
|
30
|
+
"nodejs"
|
|
31
|
+
],
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/jest": "^29.5.0",
|
|
34
|
+
"@types/node": "^20.0.0",
|
|
35
|
+
"jest": "^29.5.0",
|
|
36
|
+
"ts-jest": "^29.1.0",
|
|
37
|
+
"typescript": "^5.0.0"
|
|
38
|
+
}
|
|
39
|
+
}
|