@stvor/sdk 2.3.1 → 2.4.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/dist/facade/app.js +2 -0
- package/dist/facade/errors.d.ts +1 -0
- package/dist/facade/errors.js +1 -0
- package/dist/facade/relay-client.d.ts +12 -0
- package/dist/facade/relay-client.js +107 -35
- package/package.json +1 -1
package/dist/facade/app.js
CHANGED
|
@@ -204,6 +204,8 @@ export class StvorApp {
|
|
|
204
204
|
if (existing)
|
|
205
205
|
return existing;
|
|
206
206
|
const relay = new RelayClient(this.config.relayUrl ?? 'wss://stvor.xyz/relay', this.config.appToken, this.config.timeout ?? 10000);
|
|
207
|
+
// Wait for relay handshake - throws if API key is invalid
|
|
208
|
+
await relay.init();
|
|
207
209
|
const client = new StvorFacadeClient(userId, relay, this.config.timeout ?? 10000);
|
|
208
210
|
await client.internalInitialize();
|
|
209
211
|
this.clients.set(userId, client);
|
package/dist/facade/errors.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const Errors: {
|
|
2
2
|
readonly INVALID_APP_TOKEN: "INVALID_APP_TOKEN";
|
|
3
|
+
readonly INVALID_API_KEY: "INVALID_API_KEY";
|
|
3
4
|
readonly RELAY_UNAVAILABLE: "RELAY_UNAVAILABLE";
|
|
4
5
|
readonly DELIVERY_FAILED: "DELIVERY_FAILED";
|
|
5
6
|
readonly RECIPIENT_NOT_FOUND: "RECIPIENT_NOT_FOUND";
|
package/dist/facade/errors.js
CHANGED
|
@@ -9,16 +9,28 @@ export declare class RelayClient {
|
|
|
9
9
|
private appToken;
|
|
10
10
|
private ws?;
|
|
11
11
|
private connected;
|
|
12
|
+
private handshakeComplete;
|
|
12
13
|
private backoff;
|
|
13
14
|
private queue;
|
|
14
15
|
private handlers;
|
|
15
16
|
private reconnecting;
|
|
17
|
+
private connectPromise?;
|
|
18
|
+
private connectResolve?;
|
|
19
|
+
private connectReject?;
|
|
20
|
+
private authFailed;
|
|
16
21
|
constructor(relayUrl: string, appToken: string, timeout?: number);
|
|
22
|
+
/**
|
|
23
|
+
* Initialize the connection and wait for handshake.
|
|
24
|
+
* Throws StvorError if API key is rejected.
|
|
25
|
+
*/
|
|
26
|
+
init(): Promise<void>;
|
|
17
27
|
private getAuthHeaders;
|
|
18
28
|
private connect;
|
|
19
29
|
private scheduleReconnect;
|
|
30
|
+
private doSend;
|
|
20
31
|
send(obj: JSONable): void;
|
|
21
32
|
onMessage(h: RelayHandler): void;
|
|
22
33
|
isConnected(): boolean;
|
|
34
|
+
isAuthenticated(): boolean;
|
|
23
35
|
}
|
|
24
36
|
export {};
|
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* STVOR DX Facade - Relay Client
|
|
3
3
|
*/
|
|
4
|
+
import { Errors, StvorError } from './errors.js';
|
|
4
5
|
import * as WS from 'ws';
|
|
5
6
|
export class RelayClient {
|
|
6
7
|
constructor(relayUrl, appToken, timeout = 10000) {
|
|
7
8
|
this.connected = false;
|
|
9
|
+
this.handshakeComplete = false;
|
|
8
10
|
this.backoff = 1000;
|
|
9
11
|
this.queue = [];
|
|
10
12
|
this.handlers = [];
|
|
11
13
|
this.reconnecting = false;
|
|
14
|
+
this.authFailed = false;
|
|
12
15
|
this.relayUrl = relayUrl.replace(/^http/, 'ws');
|
|
13
16
|
this.appToken = appToken;
|
|
14
17
|
this.timeout = timeout;
|
|
15
|
-
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Initialize the connection and wait for handshake.
|
|
21
|
+
* Throws StvorError if API key is rejected.
|
|
22
|
+
*/
|
|
23
|
+
async init() {
|
|
24
|
+
if (this.authFailed) {
|
|
25
|
+
throw new StvorError(Errors.INVALID_API_KEY, 'Relay rejected connection: invalid API key');
|
|
26
|
+
}
|
|
27
|
+
if (this.handshakeComplete)
|
|
28
|
+
return;
|
|
29
|
+
await this.connect();
|
|
16
30
|
}
|
|
17
31
|
getAuthHeaders() {
|
|
18
32
|
return {
|
|
@@ -20,39 +34,88 @@ export class RelayClient {
|
|
|
20
34
|
};
|
|
21
35
|
}
|
|
22
36
|
connect() {
|
|
37
|
+
if (this.connectPromise)
|
|
38
|
+
return this.connectPromise;
|
|
23
39
|
if (this.ws)
|
|
24
|
-
return;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
this.
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
40
|
+
return Promise.resolve();
|
|
41
|
+
this.connectPromise = new Promise((resolve, reject) => {
|
|
42
|
+
this.connectResolve = resolve;
|
|
43
|
+
this.connectReject = reject;
|
|
44
|
+
const WSClass = WS.default ?? WS;
|
|
45
|
+
this.ws = new WSClass(this.relayUrl, { headers: this.getAuthHeaders() });
|
|
46
|
+
// Timeout for handshake
|
|
47
|
+
const handshakeTimeout = setTimeout(() => {
|
|
48
|
+
if (!this.handshakeComplete) {
|
|
49
|
+
this.ws?.close();
|
|
50
|
+
reject(new StvorError(Errors.RELAY_UNAVAILABLE, 'Relay handshake timeout'));
|
|
51
|
+
}
|
|
52
|
+
}, this.timeout);
|
|
53
|
+
this.ws.on('open', () => {
|
|
54
|
+
this.connected = true;
|
|
55
|
+
this.backoff = 1000;
|
|
56
|
+
// Don't flush queue yet - wait for handshake
|
|
57
|
+
});
|
|
58
|
+
this.ws.on('message', (data) => {
|
|
59
|
+
try {
|
|
60
|
+
const json = JSON.parse(data.toString());
|
|
61
|
+
// Handle handshake response
|
|
62
|
+
if (json.type === 'handshake') {
|
|
63
|
+
clearTimeout(handshakeTimeout);
|
|
64
|
+
if (json.status === 'ok') {
|
|
65
|
+
this.handshakeComplete = true;
|
|
66
|
+
// Now flush the queue
|
|
67
|
+
while (this.queue.length) {
|
|
68
|
+
const m = this.queue.shift();
|
|
69
|
+
this.doSend(m);
|
|
70
|
+
}
|
|
71
|
+
this.connectResolve?.();
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Handshake rejected
|
|
75
|
+
this.authFailed = true;
|
|
76
|
+
this.ws?.close();
|
|
77
|
+
const err = new StvorError(Errors.INVALID_API_KEY, `Relay rejected connection: ${json.reason || 'invalid API key'}`);
|
|
78
|
+
this.connectReject?.(err);
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// Regular message
|
|
83
|
+
for (const h of this.handlers)
|
|
84
|
+
h(json);
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
// ignore parse errors
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
this.ws.on('close', (code) => {
|
|
91
|
+
this.connected = false;
|
|
92
|
+
this.handshakeComplete = false;
|
|
93
|
+
this.ws = undefined;
|
|
94
|
+
this.connectPromise = undefined;
|
|
95
|
+
// If auth failed, don't reconnect
|
|
96
|
+
if (this.authFailed) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// 401/403 close codes mean auth failure
|
|
100
|
+
if (code === 4001 || code === 4003) {
|
|
101
|
+
this.authFailed = true;
|
|
102
|
+
this.connectReject?.(new StvorError(Errors.INVALID_API_KEY, 'Relay rejected connection: invalid API key'));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.scheduleReconnect();
|
|
106
|
+
});
|
|
107
|
+
this.ws.on('error', (err) => {
|
|
108
|
+
this.connected = false;
|
|
109
|
+
this.handshakeComplete = false;
|
|
110
|
+
this.ws = undefined;
|
|
111
|
+
this.connectPromise = undefined;
|
|
112
|
+
if (this.authFailed) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
this.scheduleReconnect();
|
|
116
|
+
});
|
|
55
117
|
});
|
|
118
|
+
return this.connectPromise;
|
|
56
119
|
}
|
|
57
120
|
scheduleReconnect() {
|
|
58
121
|
if (this.reconnecting)
|
|
@@ -64,19 +127,28 @@ export class RelayClient {
|
|
|
64
127
|
this.backoff = Math.min(this.backoff * 2, 30000);
|
|
65
128
|
}, this.backoff);
|
|
66
129
|
}
|
|
67
|
-
|
|
130
|
+
doSend(obj) {
|
|
68
131
|
const data = JSON.stringify(obj);
|
|
69
|
-
if (this.connected && this.ws) {
|
|
132
|
+
if (this.connected && this.ws && this.handshakeComplete) {
|
|
70
133
|
this.ws.send(data);
|
|
71
134
|
}
|
|
72
135
|
else {
|
|
73
136
|
this.queue.push(obj);
|
|
74
137
|
}
|
|
75
138
|
}
|
|
139
|
+
send(obj) {
|
|
140
|
+
if (this.authFailed) {
|
|
141
|
+
throw new StvorError(Errors.INVALID_API_KEY, 'Cannot send: relay rejected connection due to invalid API key');
|
|
142
|
+
}
|
|
143
|
+
this.doSend(obj);
|
|
144
|
+
}
|
|
76
145
|
onMessage(h) {
|
|
77
146
|
this.handlers.push(h);
|
|
78
147
|
}
|
|
79
148
|
isConnected() {
|
|
80
|
-
return this.connected;
|
|
149
|
+
return this.connected && this.handshakeComplete;
|
|
150
|
+
}
|
|
151
|
+
isAuthenticated() {
|
|
152
|
+
return this.handshakeComplete && !this.authFailed;
|
|
81
153
|
}
|
|
82
154
|
}
|