doover-js 0.1.2 → 0.2.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/README.md +104 -10
- package/dist/apis/connections-api.d.ts +6 -0
- package/dist/apis/connections-api.js +8 -0
- package/dist/auth/auth-profile.d.ts +45 -0
- package/dist/auth/auth-profile.js +72 -0
- package/dist/auth/auth-store.d.ts +14 -0
- package/dist/auth/auth-store.js +2 -0
- package/dist/auth/build-auth.d.ts +37 -0
- package/dist/auth/build-auth.js +75 -0
- package/dist/auth/cookie-auth.d.ts +17 -0
- package/dist/auth/cookie-auth.js +29 -0
- package/dist/auth/doover-auth.d.ts +41 -0
- package/dist/auth/doover-auth.js +24 -0
- package/dist/auth/doover-token-auth.d.ts +35 -0
- package/dist/auth/doover-token-auth.js +178 -0
- package/dist/auth/errors.d.ts +3 -0
- package/dist/auth/errors.js +10 -0
- package/dist/auth/jwt.d.ts +6 -0
- package/dist/auth/jwt.js +29 -0
- package/dist/client/doover-client.d.ts +15 -0
- package/dist/client/doover-client.js +45 -4
- package/dist/client/singleton.d.ts +26 -0
- package/dist/client/singleton.js +59 -0
- package/dist/client/stats.d.ts +55 -0
- package/dist/client/stats.js +97 -0
- package/dist/gateway/gateway-client.d.ts +34 -8
- package/dist/gateway/gateway-client.js +147 -52
- package/dist/gateway/types.d.ts +0 -1
- package/dist/http/rest-client.d.ts +21 -3
- package/dist/http/rest-client.js +33 -2
- package/dist/index.d.ts +12 -0
- package/dist/index.js +23 -5
- package/dist/node/config-manager.d.ts +34 -0
- package/dist/node/config-manager.js +177 -0
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +5 -0
- package/dist/react/context.d.ts +19 -0
- package/dist/react/context.js +28 -0
- package/dist/react/index.d.ts +24 -0
- package/dist/react/index.js +40 -0
- package/dist/react/sharedQueryClient.d.ts +21 -0
- package/dist/react/sharedQueryClient.js +39 -0
- package/dist/react/useAgentChannel.d.ts +7 -0
- package/dist/react/useAgentChannel.js +12 -0
- package/dist/react/useAgentConnections.d.ts +8 -0
- package/dist/react/useAgentConnections.js +24 -0
- package/dist/react/useChannelAggregate.d.ts +13 -0
- package/dist/react/useChannelAggregate.js +38 -0
- package/dist/react/useChannelMessages.d.ts +23 -0
- package/dist/react/useChannelMessages.js +56 -0
- package/dist/react/useChannelSubscription.d.ts +16 -0
- package/dist/react/useChannelSubscription.js +43 -0
- package/dist/react/useConnectionState.d.ts +21 -0
- package/dist/react/useConnectionState.js +43 -0
- package/dist/react/useMultiAgentAggregates.d.ts +22 -0
- package/dist/react/useMultiAgentAggregates.js +82 -0
- package/dist/react/useMultiAgentChannelMessages.d.ts +18 -0
- package/dist/react/useMultiAgentChannelMessages.js +78 -0
- package/dist/react/useSendMessage.d.ts +9 -0
- package/dist/react/useSendMessage.js +16 -0
- package/dist/react/useSendRpc.d.ts +61 -0
- package/dist/react/useSendRpc.js +158 -0
- package/dist/react/useTurnCredentials.d.ts +12 -0
- package/dist/react/useTurnCredentials.js +25 -0
- package/dist/react/useUpdateAggregate.d.ts +14 -0
- package/dist/react/useUpdateAggregate.js +25 -0
- package/dist/react/useUpdateMessage.d.ts +17 -0
- package/dist/react/useUpdateMessage.js +24 -0
- package/dist/test/apis.test.js +7 -1
- package/dist/test/auth.test.d.ts +1 -0
- package/dist/test/auth.test.js +502 -0
- package/dist/test/config-manager.test.d.ts +1 -0
- package/dist/test/config-manager.test.js +160 -0
- package/dist/test/doover-client.test.js +13 -0
- package/dist/test/doover-data-provider.test.js +155 -0
- package/dist/test/gateway-client.test.js +122 -8
- package/dist/test/react.test.d.ts +1 -0
- package/dist/test/react.test.js +353 -0
- package/dist/test/singleton.test.d.ts +1 -0
- package/dist/test/singleton.test.js +62 -0
- package/dist/types/common.d.ts +40 -0
- package/dist/viewer/doover-data-provider.d.ts +34 -5
- package/dist/viewer/doover-data-provider.js +96 -9
- package/package.json +33 -2
|
@@ -3,15 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.GatewayClient = void 0;
|
|
4
4
|
const errors_1 = require("../http/errors");
|
|
5
5
|
const snowflake_1 = require("../utils/snowflake");
|
|
6
|
+
const RECONNECT_BASE_MS = 1000;
|
|
7
|
+
const RECONNECT_CAP_MS = 30000;
|
|
6
8
|
class GatewayClient {
|
|
7
|
-
constructor(config) {
|
|
9
|
+
constructor(config, auth) {
|
|
8
10
|
this.config = config;
|
|
9
11
|
this.socket = null;
|
|
10
12
|
this.session = null;
|
|
11
|
-
this.heartbeatTimer = null;
|
|
12
|
-
this.lastHeartbeatAt = null;
|
|
13
|
-
this.missedHeartbeats = 0;
|
|
14
13
|
this.reconnectTimer = null;
|
|
14
|
+
this.reconnectAttempts = 0;
|
|
15
|
+
/** Set when the consumer explicitly disconnects — suppresses reconnect. */
|
|
16
|
+
this.explicitlyDisconnected = false;
|
|
17
|
+
/** Guards against concurrent connect() callers creating duplicate sockets. */
|
|
18
|
+
this.opening = false;
|
|
15
19
|
this.listeners = {
|
|
16
20
|
ready: new Set(),
|
|
17
21
|
channelSync: new Set(),
|
|
@@ -26,31 +30,87 @@ class GatewayClient {
|
|
|
26
30
|
sessionCancelled: new Set(),
|
|
27
31
|
open: new Set(),
|
|
28
32
|
close: new Set(),
|
|
29
|
-
heartbeatAck: new Set(),
|
|
30
33
|
};
|
|
31
34
|
this.subscriptions = new Map();
|
|
35
|
+
this.stats = null;
|
|
36
|
+
this.handleVisibilityChange = () => {
|
|
37
|
+
if (typeof document === "undefined")
|
|
38
|
+
return;
|
|
39
|
+
if (document.visibilityState !== "visible")
|
|
40
|
+
return;
|
|
41
|
+
this.tryImmediateReconnect();
|
|
42
|
+
};
|
|
43
|
+
this.handleOnline = () => {
|
|
44
|
+
this.tryImmediateReconnect();
|
|
45
|
+
};
|
|
46
|
+
this.auth = auth ?? null;
|
|
47
|
+
this.installLifecycleListeners();
|
|
48
|
+
}
|
|
49
|
+
/** Attach a stats collector. Call with `null` to detach. */
|
|
50
|
+
setStats(stats) {
|
|
51
|
+
this.stats = stats;
|
|
32
52
|
}
|
|
33
|
-
connect() {
|
|
53
|
+
async connect() {
|
|
54
|
+
this.explicitlyDisconnected = false;
|
|
34
55
|
if (this.socket && this.socket.readyState <= WebSocket.OPEN) {
|
|
35
56
|
return;
|
|
36
57
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
58
|
+
// De-dupe concurrent callers. Without this, N simultaneous
|
|
59
|
+
// `subscribeToChannel(...)` calls each await auth, then each create
|
|
60
|
+
// their own socket — the last one wins `this.socket`, but the earlier
|
|
61
|
+
// sockets' `onmessage` handlers still fire and try to send() through
|
|
62
|
+
// `this.socket` (a newer, not-yet-open socket → throws). The flag is
|
|
63
|
+
// cleared synchronously as soon as the new socket is assigned in
|
|
64
|
+
// `openSocket`, so reconnects after close still work.
|
|
65
|
+
if (this.opening) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this.opening = true;
|
|
69
|
+
try {
|
|
70
|
+
await this.openSocket();
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
this.opening = false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async openSocket() {
|
|
77
|
+
if (this.auth) {
|
|
78
|
+
await this.auth.ensureReady();
|
|
79
|
+
}
|
|
80
|
+
const hasFactory = !!this.config.webSocketFactory;
|
|
81
|
+
const wsParams = this.auth
|
|
82
|
+
? await this.auth.prepareWebSocket(this.config.dataWssUrl, hasFactory)
|
|
83
|
+
: { url: this.config.dataWssUrl };
|
|
84
|
+
let socket;
|
|
85
|
+
if (this.config.webSocketFactory) {
|
|
86
|
+
socket = this.config.webSocketFactory({
|
|
87
|
+
url: wsParams.url,
|
|
88
|
+
headers: wsParams.headers,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const WebSocketImpl = this.config.webSocketImpl ?? WebSocket;
|
|
93
|
+
socket = new WebSocketImpl(wsParams.url);
|
|
94
|
+
}
|
|
95
|
+
this.socket = socket;
|
|
96
|
+
// Release the concurrency gate synchronously now that the new socket is
|
|
97
|
+
// assigned — any subsequent connect() will see it via `this.socket`.
|
|
98
|
+
this.opening = false;
|
|
99
|
+
socket.onopen = () => this.emit("open");
|
|
100
|
+
socket.onclose = (event) => {
|
|
42
101
|
this.emit("close", event);
|
|
43
102
|
this.scheduleReconnect();
|
|
44
103
|
};
|
|
45
|
-
|
|
46
|
-
|
|
104
|
+
socket.onerror = () => undefined;
|
|
105
|
+
socket.onmessage = (event) => this.handleMessage(event.data);
|
|
47
106
|
}
|
|
48
107
|
disconnect(code, reason) {
|
|
49
|
-
this.
|
|
108
|
+
this.explicitlyDisconnected = true;
|
|
50
109
|
if (this.reconnectTimer) {
|
|
51
110
|
clearTimeout(this.reconnectTimer);
|
|
52
111
|
this.reconnectTimer = null;
|
|
53
112
|
}
|
|
113
|
+
this.reconnectAttempts = 0;
|
|
54
114
|
this.socket?.close(code, reason);
|
|
55
115
|
this.socket = null;
|
|
56
116
|
}
|
|
@@ -66,7 +126,7 @@ class GatewayClient {
|
|
|
66
126
|
diff_only: options?.diff_only ?? false,
|
|
67
127
|
});
|
|
68
128
|
if (!this.isConnected()) {
|
|
69
|
-
this.connect();
|
|
129
|
+
void this.connect();
|
|
70
130
|
return;
|
|
71
131
|
}
|
|
72
132
|
this.send({
|
|
@@ -93,7 +153,7 @@ class GatewayClient {
|
|
|
93
153
|
}
|
|
94
154
|
syncChannel(channel) {
|
|
95
155
|
if (!this.isConnected()) {
|
|
96
|
-
this.connect();
|
|
156
|
+
void this.connect();
|
|
97
157
|
return;
|
|
98
158
|
}
|
|
99
159
|
this.send({
|
|
@@ -106,7 +166,7 @@ class GatewayClient {
|
|
|
106
166
|
}
|
|
107
167
|
sendOneShotMessage(channel, data) {
|
|
108
168
|
if (!this.isConnected()) {
|
|
109
|
-
this.connect();
|
|
169
|
+
void this.connect();
|
|
110
170
|
return;
|
|
111
171
|
}
|
|
112
172
|
this.send({
|
|
@@ -120,20 +180,44 @@ class GatewayClient {
|
|
|
120
180
|
getSession() {
|
|
121
181
|
return this.session;
|
|
122
182
|
}
|
|
123
|
-
getLatency() {
|
|
124
|
-
return this.lastHeartbeatAt === null ? null : Date.now() - this.lastHeartbeatAt;
|
|
125
|
-
}
|
|
126
183
|
isConnected() {
|
|
127
184
|
return this.socket?.readyState === WebSocket.OPEN;
|
|
128
185
|
}
|
|
186
|
+
/** Number of channels the gateway is currently subscribed to. */
|
|
187
|
+
getSubscriptionCount() {
|
|
188
|
+
return this.subscriptions.size;
|
|
189
|
+
}
|
|
190
|
+
/** Snapshot of currently subscribed channels (for debug/inspection). */
|
|
191
|
+
getSubscriptions() {
|
|
192
|
+
return [...this.subscriptions.values()].map((s) => s.channel);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Force a fresh connection: close the current socket (without suppressing
|
|
196
|
+
* reconnects) and immediately open a new one. Useful for a manual
|
|
197
|
+
* "reconnect now" control in debug UIs.
|
|
198
|
+
*/
|
|
199
|
+
async reconnect() {
|
|
200
|
+
if (this.reconnectTimer) {
|
|
201
|
+
clearTimeout(this.reconnectTimer);
|
|
202
|
+
this.reconnectTimer = null;
|
|
203
|
+
}
|
|
204
|
+
this.reconnectAttempts = 0;
|
|
205
|
+
// Drop the session so the new socket identifies fresh (op 10) rather
|
|
206
|
+
// than attempting to resume a just-closed session (op 11) that the
|
|
207
|
+
// server has already torn down.
|
|
208
|
+
this.session = null;
|
|
209
|
+
if (this.socket) {
|
|
210
|
+
// Suppress the auto-reconnect path so we don't double-schedule.
|
|
211
|
+
const prev = this.socket;
|
|
212
|
+
prev.onclose = null;
|
|
213
|
+
prev.close(1000, "manual reconnect");
|
|
214
|
+
this.socket = null;
|
|
215
|
+
}
|
|
216
|
+
await this.connect();
|
|
217
|
+
}
|
|
129
218
|
handleMessage(raw) {
|
|
219
|
+
this.stats?.recordGatewayReceived();
|
|
130
220
|
const message = JSON.parse(raw);
|
|
131
|
-
if (message.op === 2) {
|
|
132
|
-
this.missedHeartbeats = 0;
|
|
133
|
-
const latency = this.lastHeartbeatAt === null ? null : Date.now() - this.lastHeartbeatAt;
|
|
134
|
-
this.emit("heartbeatAck", latency);
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
221
|
if (message.op === 3) {
|
|
138
222
|
this.session = null;
|
|
139
223
|
this.emit("sessionCancelled");
|
|
@@ -145,9 +229,9 @@ class GatewayClient {
|
|
|
145
229
|
switch (message.t) {
|
|
146
230
|
case "Hello":
|
|
147
231
|
this.identifyOrResume();
|
|
148
|
-
this.startHeartbeat();
|
|
149
232
|
break;
|
|
150
233
|
case "Ready":
|
|
234
|
+
this.reconnectAttempts = 0;
|
|
151
235
|
this.session = message.d;
|
|
152
236
|
this.emit("ready", message.d);
|
|
153
237
|
this.resubscribeAll();
|
|
@@ -209,33 +293,12 @@ class GatewayClient {
|
|
|
209
293
|
this.subscribe(entry.channel, { diff_only: entry.diff_only });
|
|
210
294
|
}
|
|
211
295
|
}
|
|
212
|
-
startHeartbeat() {
|
|
213
|
-
this.stopHeartbeat();
|
|
214
|
-
this.heartbeatTimer = setInterval(() => {
|
|
215
|
-
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
this.lastHeartbeatAt = Date.now();
|
|
219
|
-
this.missedHeartbeats += 1;
|
|
220
|
-
if (this.missedHeartbeats > 3) {
|
|
221
|
-
this.socket.close(4000, "Missed heartbeats");
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
this.send({ op: 1, d: {} });
|
|
225
|
-
}, 20000);
|
|
226
|
-
}
|
|
227
|
-
stopHeartbeat() {
|
|
228
|
-
if (this.heartbeatTimer) {
|
|
229
|
-
clearInterval(this.heartbeatTimer);
|
|
230
|
-
this.heartbeatTimer = null;
|
|
231
|
-
}
|
|
232
|
-
this.missedHeartbeats = 0;
|
|
233
|
-
}
|
|
234
296
|
send(payload) {
|
|
235
297
|
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
236
298
|
throw new errors_1.DooverGatewayError("WebSocket is not connected");
|
|
237
299
|
}
|
|
238
300
|
this.socket.send(JSON.stringify(payload));
|
|
301
|
+
this.stats?.recordGatewaySent();
|
|
239
302
|
}
|
|
240
303
|
emit(eventName, ...args) {
|
|
241
304
|
this.listeners[eventName].forEach((listener) => {
|
|
@@ -243,13 +306,45 @@ class GatewayClient {
|
|
|
243
306
|
});
|
|
244
307
|
}
|
|
245
308
|
scheduleReconnect() {
|
|
246
|
-
if (this.reconnectTimer) {
|
|
309
|
+
if (this.reconnectTimer || this.explicitlyDisconnected) {
|
|
247
310
|
return;
|
|
248
311
|
}
|
|
312
|
+
const delay = this.computeReconnectDelay();
|
|
313
|
+
this.reconnectAttempts += 1;
|
|
249
314
|
this.reconnectTimer = setTimeout(() => {
|
|
250
315
|
this.reconnectTimer = null;
|
|
251
|
-
this.connect();
|
|
252
|
-
},
|
|
316
|
+
void this.connect();
|
|
317
|
+
}, delay);
|
|
318
|
+
}
|
|
319
|
+
/** Exponential backoff with full-jitter, capped at RECONNECT_CAP_MS. */
|
|
320
|
+
computeReconnectDelay() {
|
|
321
|
+
const exp = Math.min(RECONNECT_CAP_MS, RECONNECT_BASE_MS * 2 ** this.reconnectAttempts);
|
|
322
|
+
return Math.floor(Math.random() * exp);
|
|
323
|
+
}
|
|
324
|
+
installLifecycleListeners() {
|
|
325
|
+
if (this.config.disableBrowserLifecycleHooks)
|
|
326
|
+
return;
|
|
327
|
+
if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
|
|
328
|
+
document.addEventListener("visibilitychange", this.handleVisibilityChange);
|
|
329
|
+
}
|
|
330
|
+
if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
|
|
331
|
+
window.addEventListener("online", this.handleOnline);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Called by lifecycle hooks when we have a strong signal that the network
|
|
336
|
+
* or tab has come back — skip the backoff schedule and reconnect now.
|
|
337
|
+
*/
|
|
338
|
+
tryImmediateReconnect() {
|
|
339
|
+
if (this.explicitlyDisconnected)
|
|
340
|
+
return;
|
|
341
|
+
if (this.isConnected())
|
|
342
|
+
return;
|
|
343
|
+
if (this.reconnectTimer) {
|
|
344
|
+
clearTimeout(this.reconnectTimer);
|
|
345
|
+
this.reconnectTimer = null;
|
|
346
|
+
}
|
|
347
|
+
void this.connect();
|
|
253
348
|
}
|
|
254
349
|
channelKey(channel) {
|
|
255
350
|
return `${channel.agent_id}/${channel.name}`;
|
package/dist/gateway/types.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import type { AuthConfig } from "../auth/build-auth";
|
|
2
|
+
import type { DooverAuth } from "../auth/doover-auth";
|
|
3
|
+
import type { DooverStatsCollector } from "../client/stats";
|
|
4
|
+
export interface DooverClientConfig extends AuthConfig {
|
|
2
5
|
dataRestUrl: string;
|
|
3
6
|
controlApiUrl: string;
|
|
4
7
|
dataWssUrl: string;
|
|
@@ -7,6 +10,16 @@ export interface DooverClientConfig {
|
|
|
7
10
|
impersonateUserStorageKey?: string;
|
|
8
11
|
fetchImpl?: typeof fetch;
|
|
9
12
|
webSocketImpl?: typeof WebSocket;
|
|
13
|
+
webSocketFactory?: (options: {
|
|
14
|
+
url: string;
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
}) => WebSocket;
|
|
17
|
+
/**
|
|
18
|
+
* When true, disables the GatewayClient's browser lifecycle listeners
|
|
19
|
+
* (visibilitychange, online). Defaults to false — hooks are on by default
|
|
20
|
+
* in browsers and automatically skipped in non-browser environments.
|
|
21
|
+
*/
|
|
22
|
+
disableBrowserLifecycleHooks?: boolean;
|
|
10
23
|
}
|
|
11
24
|
export interface RequestOptions {
|
|
12
25
|
baseUrl?: string;
|
|
@@ -20,9 +33,14 @@ export interface RequestOptions {
|
|
|
20
33
|
type QueryValue = string | number | boolean | null | undefined | Array<string | number | boolean>;
|
|
21
34
|
type QueryParams = Record<string, QueryValue> | object;
|
|
22
35
|
export declare class RestClient {
|
|
23
|
-
readonly config: Required<Pick<DooverClientConfig, "dataRestUrl" | "controlApiUrl" | "dataWssUrl" | "sharing" | "impersonateUserStorageKey">> & Pick<DooverClientConfig, "organisationId" | "fetchImpl" | "webSocketImpl">;
|
|
24
|
-
|
|
36
|
+
readonly config: Required<Pick<DooverClientConfig, "dataRestUrl" | "controlApiUrl" | "dataWssUrl" | "sharing" | "impersonateUserStorageKey">> & Pick<DooverClientConfig, "organisationId" | "fetchImpl" | "webSocketImpl" | "webSocketFactory">;
|
|
37
|
+
readonly auth: DooverAuth | null;
|
|
38
|
+
private stats;
|
|
39
|
+
constructor(config: DooverClientConfig, auth?: DooverAuth);
|
|
40
|
+
/** Attach a stats collector. Call with `null` to detach. */
|
|
41
|
+
setStats(stats: DooverStatsCollector | null): void;
|
|
25
42
|
request<T>(options: RequestOptions): Promise<T>;
|
|
43
|
+
private doRequest;
|
|
26
44
|
get<T>(path: string, query?: RequestOptions["query"], baseUrl?: string): Promise<T>;
|
|
27
45
|
post<T>(path: string, body?: RequestOptions["body"], query?: RequestOptions["query"], baseUrl?: string): Promise<T>;
|
|
28
46
|
put<T>(path: string, body?: RequestOptions["body"], query?: RequestOptions["query"], baseUrl?: string): Promise<T>;
|
package/dist/http/rest-client.js
CHANGED
|
@@ -3,19 +3,47 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.RestClient = void 0;
|
|
4
4
|
const errors_1 = require("./errors");
|
|
5
5
|
class RestClient {
|
|
6
|
-
constructor(config) {
|
|
6
|
+
constructor(config, auth) {
|
|
7
|
+
this.stats = null;
|
|
7
8
|
this.config = {
|
|
8
9
|
...config,
|
|
9
10
|
sharing: config.sharing ?? "internal",
|
|
10
11
|
organisationId: config.organisationId ?? null,
|
|
11
12
|
impersonateUserStorageKey: config.impersonateUserStorageKey ?? "impersonate_user_id",
|
|
12
13
|
};
|
|
14
|
+
this.auth = auth ?? null;
|
|
15
|
+
}
|
|
16
|
+
/** Attach a stats collector. Call with `null` to detach. */
|
|
17
|
+
setStats(stats) {
|
|
18
|
+
this.stats = stats;
|
|
13
19
|
}
|
|
14
20
|
async request(options) {
|
|
21
|
+
const startedAt = this.stats?.recordRestStart() ?? null;
|
|
22
|
+
try {
|
|
23
|
+
const payload = await this.doRequest(options);
|
|
24
|
+
this.stats?.recordRestEnd(startedAt, true);
|
|
25
|
+
return payload;
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
this.stats?.recordRestEnd(startedAt, false);
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async doRequest(options) {
|
|
33
|
+
if (this.auth) {
|
|
34
|
+
await this.auth.ensureReady();
|
|
35
|
+
}
|
|
15
36
|
const method = options.method ?? "GET";
|
|
16
37
|
const url = this.buildUrl(options.baseUrl ?? this.config.dataRestUrl, options.path, options.query);
|
|
17
38
|
const headers = new Headers(options.headers);
|
|
18
39
|
const body = this.normalizeBody(options.body, headers);
|
|
40
|
+
// Merge auth headers.
|
|
41
|
+
if (this.auth) {
|
|
42
|
+
const authHeaders = await this.auth.getHttpHeaders();
|
|
43
|
+
for (const [key, value] of Object.entries(authHeaders)) {
|
|
44
|
+
headers.set(key, value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
19
47
|
if (!options.omitSharingHeader) {
|
|
20
48
|
headers.set("X-Doover-Sharing", this.config.sharing);
|
|
21
49
|
}
|
|
@@ -26,12 +54,15 @@ class RestClient {
|
|
|
26
54
|
if (impersonated) {
|
|
27
55
|
headers.set("X-Doover-Assume", impersonated);
|
|
28
56
|
}
|
|
57
|
+
const credentials = this.auth
|
|
58
|
+
? this.auth.getFetchCredentials()
|
|
59
|
+
: "include";
|
|
29
60
|
const fetchImpl = this.config.fetchImpl ?? fetch;
|
|
30
61
|
const response = await fetchImpl(url, {
|
|
31
62
|
method,
|
|
32
63
|
headers,
|
|
33
64
|
body,
|
|
34
|
-
credentials
|
|
65
|
+
credentials,
|
|
35
66
|
});
|
|
36
67
|
const payload = await this.parseResponse(response);
|
|
37
68
|
if (!response.ok) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { DooverClient } from "./client/doover-client";
|
|
2
|
+
export { getDooverClient, peekDooverClient, resetDooverClient, } from "./client/singleton";
|
|
3
|
+
export { DooverStatsCollector } from "./client/stats";
|
|
4
|
+
export type { DooverStatsSnapshot, RestStatsSnapshot, GatewayStatsSnapshot, } from "./client/stats";
|
|
2
5
|
export { AgentsApi } from "./apis/agents-api";
|
|
3
6
|
export { AggregatesApi } from "./apis/aggregates-api";
|
|
4
7
|
export { AlarmsApi } from "./apis/alarms-api";
|
|
@@ -9,6 +12,15 @@ export { NotificationsApi } from "./apis/notifications-api";
|
|
|
9
12
|
export { PermissionsApi } from "./apis/permissions-api";
|
|
10
13
|
export { ProcessorsApi } from "./apis/processors-api";
|
|
11
14
|
export { TurnApi } from "./apis/turn-api";
|
|
15
|
+
export { DooverAuth } from "./auth/doover-auth";
|
|
16
|
+
export { CookieAuth } from "./auth/cookie-auth";
|
|
17
|
+
export { DooverTokenAuth } from "./auth/doover-token-auth";
|
|
18
|
+
export { AuthProfile } from "./auth/auth-profile";
|
|
19
|
+
export type { AuthProfileData } from "./auth/auth-profile";
|
|
20
|
+
export type { AuthProfileStore } from "./auth/auth-store";
|
|
21
|
+
export { DooverAuthError } from "./auth/errors";
|
|
22
|
+
export { buildAuth } from "./auth/build-auth";
|
|
23
|
+
export type { AuthConfig } from "./auth/build-auth";
|
|
12
24
|
export { GatewayClient } from "./gateway/gateway-client";
|
|
13
25
|
export type * from "./gateway/types";
|
|
14
26
|
export { RestClient } from "./http/rest-client";
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.generateSnowflakeIdAtTime = exports.extractSnowflakeId = exports.addTimestampToMessage = exports.getIdentifierFromPath = exports.DooverDataProvider = exports.DooverValidationError = exports.DooverGatewayError = exports.DooverApiError = exports.RestClient = exports.GatewayClient = exports.TurnApi = exports.ProcessorsApi = exports.PermissionsApi = exports.NotificationsApi = exports.MessagesApi = exports.ConnectionsApi = exports.ChannelsApi = exports.AlarmsApi = exports.AggregatesApi = exports.AgentsApi = exports.DooverClient = void 0;
|
|
3
|
+
exports.generateSnowflakeIdAtTime = exports.extractSnowflakeId = exports.addTimestampToMessage = exports.getIdentifierFromPath = exports.DooverDataProvider = exports.DooverValidationError = exports.DooverGatewayError = exports.DooverApiError = exports.RestClient = exports.GatewayClient = exports.buildAuth = exports.DooverAuthError = exports.AuthProfile = exports.DooverTokenAuth = exports.CookieAuth = exports.DooverAuth = exports.TurnApi = exports.ProcessorsApi = exports.PermissionsApi = exports.NotificationsApi = exports.MessagesApi = exports.ConnectionsApi = exports.ChannelsApi = exports.AlarmsApi = exports.AggregatesApi = exports.AgentsApi = exports.DooverStatsCollector = exports.resetDooverClient = exports.peekDooverClient = exports.getDooverClient = exports.DooverClient = void 0;
|
|
4
4
|
var doover_client_1 = require("./client/doover-client");
|
|
5
5
|
Object.defineProperty(exports, "DooverClient", { enumerable: true, get: function () { return doover_client_1.DooverClient; } });
|
|
6
|
+
var singleton_1 = require("./client/singleton");
|
|
7
|
+
Object.defineProperty(exports, "getDooverClient", { enumerable: true, get: function () { return singleton_1.getDooverClient; } });
|
|
8
|
+
Object.defineProperty(exports, "peekDooverClient", { enumerable: true, get: function () { return singleton_1.peekDooverClient; } });
|
|
9
|
+
Object.defineProperty(exports, "resetDooverClient", { enumerable: true, get: function () { return singleton_1.resetDooverClient; } });
|
|
10
|
+
var stats_1 = require("./client/stats");
|
|
11
|
+
Object.defineProperty(exports, "DooverStatsCollector", { enumerable: true, get: function () { return stats_1.DooverStatsCollector; } });
|
|
6
12
|
var agents_api_1 = require("./apis/agents-api");
|
|
7
13
|
Object.defineProperty(exports, "AgentsApi", { enumerable: true, get: function () { return agents_api_1.AgentsApi; } });
|
|
8
14
|
var aggregates_api_1 = require("./apis/aggregates-api");
|
|
@@ -23,14 +29,26 @@ var processors_api_1 = require("./apis/processors-api");
|
|
|
23
29
|
Object.defineProperty(exports, "ProcessorsApi", { enumerable: true, get: function () { return processors_api_1.ProcessorsApi; } });
|
|
24
30
|
var turn_api_1 = require("./apis/turn-api");
|
|
25
31
|
Object.defineProperty(exports, "TurnApi", { enumerable: true, get: function () { return turn_api_1.TurnApi; } });
|
|
32
|
+
var doover_auth_1 = require("./auth/doover-auth");
|
|
33
|
+
Object.defineProperty(exports, "DooverAuth", { enumerable: true, get: function () { return doover_auth_1.DooverAuth; } });
|
|
34
|
+
var cookie_auth_1 = require("./auth/cookie-auth");
|
|
35
|
+
Object.defineProperty(exports, "CookieAuth", { enumerable: true, get: function () { return cookie_auth_1.CookieAuth; } });
|
|
36
|
+
var doover_token_auth_1 = require("./auth/doover-token-auth");
|
|
37
|
+
Object.defineProperty(exports, "DooverTokenAuth", { enumerable: true, get: function () { return doover_token_auth_1.DooverTokenAuth; } });
|
|
38
|
+
var auth_profile_1 = require("./auth/auth-profile");
|
|
39
|
+
Object.defineProperty(exports, "AuthProfile", { enumerable: true, get: function () { return auth_profile_1.AuthProfile; } });
|
|
40
|
+
var errors_1 = require("./auth/errors");
|
|
41
|
+
Object.defineProperty(exports, "DooverAuthError", { enumerable: true, get: function () { return errors_1.DooverAuthError; } });
|
|
42
|
+
var build_auth_1 = require("./auth/build-auth");
|
|
43
|
+
Object.defineProperty(exports, "buildAuth", { enumerable: true, get: function () { return build_auth_1.buildAuth; } });
|
|
26
44
|
var gateway_client_1 = require("./gateway/gateway-client");
|
|
27
45
|
Object.defineProperty(exports, "GatewayClient", { enumerable: true, get: function () { return gateway_client_1.GatewayClient; } });
|
|
28
46
|
var rest_client_1 = require("./http/rest-client");
|
|
29
47
|
Object.defineProperty(exports, "RestClient", { enumerable: true, get: function () { return rest_client_1.RestClient; } });
|
|
30
|
-
var
|
|
31
|
-
Object.defineProperty(exports, "DooverApiError", { enumerable: true, get: function () { return
|
|
32
|
-
Object.defineProperty(exports, "DooverGatewayError", { enumerable: true, get: function () { return
|
|
33
|
-
Object.defineProperty(exports, "DooverValidationError", { enumerable: true, get: function () { return
|
|
48
|
+
var errors_2 = require("./http/errors");
|
|
49
|
+
Object.defineProperty(exports, "DooverApiError", { enumerable: true, get: function () { return errors_2.DooverApiError; } });
|
|
50
|
+
Object.defineProperty(exports, "DooverGatewayError", { enumerable: true, get: function () { return errors_2.DooverGatewayError; } });
|
|
51
|
+
Object.defineProperty(exports, "DooverValidationError", { enumerable: true, get: function () { return errors_2.DooverValidationError; } });
|
|
34
52
|
var doover_data_provider_1 = require("./viewer/doover-data-provider");
|
|
35
53
|
Object.defineProperty(exports, "DooverDataProvider", { enumerable: true, get: function () { return doover_data_provider_1.DooverDataProvider; } });
|
|
36
54
|
var path_parsing_1 = require("./viewer/path-parsing");
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AuthProfile } from "../auth/auth-profile";
|
|
2
|
+
import type { AuthProfileStore } from "../auth/auth-store";
|
|
3
|
+
/**
|
|
4
|
+
* File-backed profile store compatible with pydoover's config format.
|
|
5
|
+
*
|
|
6
|
+
* Reads `~/.doover/config` (or a custom path) synchronously on construction,
|
|
7
|
+
* matching Python's eager-read model.
|
|
8
|
+
*
|
|
9
|
+
* Behaviour:
|
|
10
|
+
* - Doover2 `[profile=...]` blocks are parsed into `AuthProfile` entries.
|
|
11
|
+
* - Legacy Doover1 `[...]` blocks and unrecognised raw blocks are preserved
|
|
12
|
+
* verbatim in their original order and written back unchanged.
|
|
13
|
+
* - `write()` creates the `~/.doover` directory if missing.
|
|
14
|
+
*/
|
|
15
|
+
export declare class ConfigManager implements AuthProfileStore {
|
|
16
|
+
currentProfile: string | null;
|
|
17
|
+
current: AuthProfile | null;
|
|
18
|
+
/** Parsed Doover2 profiles keyed by profile name. */
|
|
19
|
+
private profiles;
|
|
20
|
+
/**
|
|
21
|
+
* Ordered list of *blocks* in the file as they were originally read. Each
|
|
22
|
+
* entry is either a `{ type: "managed"; name: string }` reference into
|
|
23
|
+
* `this.profiles`, or a `{ type: "raw"; text: string }` blob that is
|
|
24
|
+
* preserved verbatim.
|
|
25
|
+
*/
|
|
26
|
+
private blocks;
|
|
27
|
+
private readonly configPath;
|
|
28
|
+
constructor(configPath?: string);
|
|
29
|
+
get(profileName: string): AuthProfile | undefined;
|
|
30
|
+
create(entry: AuthProfile): void;
|
|
31
|
+
delete(profileName: string): void;
|
|
32
|
+
write(): void;
|
|
33
|
+
private read;
|
|
34
|
+
}
|