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.
Files changed (84) hide show
  1. package/README.md +104 -10
  2. package/dist/apis/connections-api.d.ts +6 -0
  3. package/dist/apis/connections-api.js +8 -0
  4. package/dist/auth/auth-profile.d.ts +45 -0
  5. package/dist/auth/auth-profile.js +72 -0
  6. package/dist/auth/auth-store.d.ts +14 -0
  7. package/dist/auth/auth-store.js +2 -0
  8. package/dist/auth/build-auth.d.ts +37 -0
  9. package/dist/auth/build-auth.js +75 -0
  10. package/dist/auth/cookie-auth.d.ts +17 -0
  11. package/dist/auth/cookie-auth.js +29 -0
  12. package/dist/auth/doover-auth.d.ts +41 -0
  13. package/dist/auth/doover-auth.js +24 -0
  14. package/dist/auth/doover-token-auth.d.ts +35 -0
  15. package/dist/auth/doover-token-auth.js +178 -0
  16. package/dist/auth/errors.d.ts +3 -0
  17. package/dist/auth/errors.js +10 -0
  18. package/dist/auth/jwt.d.ts +6 -0
  19. package/dist/auth/jwt.js +29 -0
  20. package/dist/client/doover-client.d.ts +15 -0
  21. package/dist/client/doover-client.js +45 -4
  22. package/dist/client/singleton.d.ts +26 -0
  23. package/dist/client/singleton.js +59 -0
  24. package/dist/client/stats.d.ts +55 -0
  25. package/dist/client/stats.js +97 -0
  26. package/dist/gateway/gateway-client.d.ts +34 -8
  27. package/dist/gateway/gateway-client.js +147 -52
  28. package/dist/gateway/types.d.ts +0 -1
  29. package/dist/http/rest-client.d.ts +21 -3
  30. package/dist/http/rest-client.js +33 -2
  31. package/dist/index.d.ts +12 -0
  32. package/dist/index.js +23 -5
  33. package/dist/node/config-manager.d.ts +34 -0
  34. package/dist/node/config-manager.js +177 -0
  35. package/dist/node/index.d.ts +1 -0
  36. package/dist/node/index.js +5 -0
  37. package/dist/react/context.d.ts +19 -0
  38. package/dist/react/context.js +28 -0
  39. package/dist/react/index.d.ts +24 -0
  40. package/dist/react/index.js +40 -0
  41. package/dist/react/sharedQueryClient.d.ts +21 -0
  42. package/dist/react/sharedQueryClient.js +39 -0
  43. package/dist/react/useAgentChannel.d.ts +7 -0
  44. package/dist/react/useAgentChannel.js +12 -0
  45. package/dist/react/useAgentConnections.d.ts +8 -0
  46. package/dist/react/useAgentConnections.js +24 -0
  47. package/dist/react/useChannelAggregate.d.ts +13 -0
  48. package/dist/react/useChannelAggregate.js +38 -0
  49. package/dist/react/useChannelMessages.d.ts +23 -0
  50. package/dist/react/useChannelMessages.js +56 -0
  51. package/dist/react/useChannelSubscription.d.ts +16 -0
  52. package/dist/react/useChannelSubscription.js +43 -0
  53. package/dist/react/useConnectionState.d.ts +21 -0
  54. package/dist/react/useConnectionState.js +43 -0
  55. package/dist/react/useMultiAgentAggregates.d.ts +22 -0
  56. package/dist/react/useMultiAgentAggregates.js +82 -0
  57. package/dist/react/useMultiAgentChannelMessages.d.ts +18 -0
  58. package/dist/react/useMultiAgentChannelMessages.js +78 -0
  59. package/dist/react/useSendMessage.d.ts +9 -0
  60. package/dist/react/useSendMessage.js +16 -0
  61. package/dist/react/useSendRpc.d.ts +61 -0
  62. package/dist/react/useSendRpc.js +158 -0
  63. package/dist/react/useTurnCredentials.d.ts +12 -0
  64. package/dist/react/useTurnCredentials.js +25 -0
  65. package/dist/react/useUpdateAggregate.d.ts +14 -0
  66. package/dist/react/useUpdateAggregate.js +25 -0
  67. package/dist/react/useUpdateMessage.d.ts +17 -0
  68. package/dist/react/useUpdateMessage.js +24 -0
  69. package/dist/test/apis.test.js +7 -1
  70. package/dist/test/auth.test.d.ts +1 -0
  71. package/dist/test/auth.test.js +502 -0
  72. package/dist/test/config-manager.test.d.ts +1 -0
  73. package/dist/test/config-manager.test.js +160 -0
  74. package/dist/test/doover-client.test.js +13 -0
  75. package/dist/test/doover-data-provider.test.js +155 -0
  76. package/dist/test/gateway-client.test.js +122 -8
  77. package/dist/test/react.test.d.ts +1 -0
  78. package/dist/test/react.test.js +353 -0
  79. package/dist/test/singleton.test.d.ts +1 -0
  80. package/dist/test/singleton.test.js +62 -0
  81. package/dist/types/common.d.ts +40 -0
  82. package/dist/viewer/doover-data-provider.d.ts +34 -5
  83. package/dist/viewer/doover-data-provider.js +96 -9
  84. package/package.json +33 -2
@@ -0,0 +1,502 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const chai_1 = require("chai");
7
+ const chai_as_promised_1 = __importDefault(require("chai-as-promised"));
8
+ const mocha_1 = require("mocha");
9
+ const sinon_1 = __importDefault(require("sinon"));
10
+ const auth_profile_1 = require("../auth/auth-profile");
11
+ const build_auth_1 = require("../auth/build-auth");
12
+ const cookie_auth_1 = require("../auth/cookie-auth");
13
+ const doover_token_auth_1 = require("../auth/doover-token-auth");
14
+ const errors_1 = require("../auth/errors");
15
+ const jwt_1 = require("../auth/jwt");
16
+ const doover_client_1 = require("../client/doover-client");
17
+ const gateway_client_1 = require("../gateway/gateway-client");
18
+ const rest_client_1 = require("../http/rest-client");
19
+ const helpers_1 = require("./helpers");
20
+ (0, chai_1.use)(chai_as_promised_1.default);
21
+ // ---------------------------------------------------------------------------
22
+ // Helper: create a minimal JWT with a given `exp` claim
23
+ // ---------------------------------------------------------------------------
24
+ function makeJwt(exp) {
25
+ const header = Buffer.from(JSON.stringify({ alg: "HS256" })).toString("base64url");
26
+ const payload = Buffer.from(JSON.stringify({ exp })).toString("base64url");
27
+ return `${header}.${payload}.fakesig`;
28
+ }
29
+ // ---------------------------------------------------------------------------
30
+ // Helper: in-memory AuthProfileStore
31
+ // ---------------------------------------------------------------------------
32
+ class MemoryProfileStore {
33
+ constructor() {
34
+ this.currentProfile = null;
35
+ this.current = null;
36
+ this.profiles = new Map();
37
+ this.writeCalled = 0;
38
+ }
39
+ get(name) {
40
+ return this.profiles.get(name);
41
+ }
42
+ create(entry) {
43
+ this.profiles.set(entry.profile, entry);
44
+ }
45
+ delete(name) {
46
+ this.profiles.delete(name);
47
+ }
48
+ write() {
49
+ this.writeCalled++;
50
+ }
51
+ }
52
+ // ---------------------------------------------------------------------------
53
+ // CookieAuth
54
+ // ---------------------------------------------------------------------------
55
+ (0, mocha_1.describe)("CookieAuth", () => {
56
+ (0, mocha_1.it)("sends no auth header and uses credentials: include", async () => {
57
+ const auth = new cookie_auth_1.CookieAuth();
58
+ (0, chai_1.expect)(await auth.getHttpHeaders()).to.deep.equal({});
59
+ (0, chai_1.expect)(auth.getFetchCredentials()).to.equal("include");
60
+ });
61
+ (0, mocha_1.it)("prepareWebSocket returns the URL unchanged", async () => {
62
+ const auth = new cookie_auth_1.CookieAuth();
63
+ const result = await auth.prepareWebSocket("wss://example.com/ws", true);
64
+ (0, chai_1.expect)(result).to.deep.equal({ url: "wss://example.com/ws" });
65
+ });
66
+ (0, mocha_1.it)("ensureReady resolves immediately", async () => {
67
+ const auth = new cookie_auth_1.CookieAuth();
68
+ await auth.ensureReady(); // should not throw
69
+ });
70
+ });
71
+ // ---------------------------------------------------------------------------
72
+ // DooverTokenAuth
73
+ // ---------------------------------------------------------------------------
74
+ (0, mocha_1.describe)("DooverTokenAuth", () => {
75
+ (0, mocha_1.afterEach)(() => sinon_1.default.restore());
76
+ (0, mocha_1.it)("sends bearer header and uses credentials: omit", async () => {
77
+ const auth = new doover_token_auth_1.DooverTokenAuth({ token: "tok123" });
78
+ (0, chai_1.expect)(await auth.getHttpHeaders()).to.deep.equal({
79
+ Authorization: "Bearer tok123",
80
+ });
81
+ (0, chai_1.expect)(auth.getFetchCredentials()).to.equal("omit");
82
+ });
83
+ (0, mocha_1.it)("returns empty headers when no token is set", async () => {
84
+ const auth = new doover_token_auth_1.DooverTokenAuth({});
85
+ (0, chai_1.expect)(await auth.getHttpHeaders()).to.deep.equal({});
86
+ });
87
+ (0, mocha_1.it)("setToken updates the token", async () => {
88
+ const auth = new doover_token_auth_1.DooverTokenAuth({ token: "old" });
89
+ auth.setToken("new-token");
90
+ (0, chai_1.expect)(await auth.getHttpHeaders()).to.deep.equal({
91
+ Authorization: "Bearer new-token",
92
+ });
93
+ });
94
+ // -- Refresh behaviour ----------------------------------------------------
95
+ (0, mocha_1.it)("does not refresh when token is clearly valid", async () => {
96
+ const farFuture = Date.now() + 3600000;
97
+ const fetchMock = (0, helpers_1.createFetchMock)();
98
+ const auth = new doover_token_auth_1.DooverTokenAuth({
99
+ token: "valid",
100
+ tokenExpires: new Date(farFuture),
101
+ refreshToken: "rt",
102
+ authServerUrl: "https://auth.example.com",
103
+ authServerClientId: "client-1",
104
+ fetchImpl: fetchMock,
105
+ });
106
+ await auth.ensureReady();
107
+ (0, chai_1.expect)(fetchMock.callCount).to.equal(0);
108
+ });
109
+ (0, mocha_1.it)("triggers refresh when token is missing", async () => {
110
+ const fetchMock = (0, helpers_1.createFetchMock)(() => (0, helpers_1.createJsonResponse)({
111
+ access_token: "new-tok",
112
+ expires_in: 3600,
113
+ }));
114
+ const auth = new doover_token_auth_1.DooverTokenAuth({
115
+ token: null,
116
+ refreshToken: "rt",
117
+ authServerUrl: "https://auth.example.com",
118
+ authServerClientId: "client-1",
119
+ fetchImpl: fetchMock,
120
+ });
121
+ await auth.ensureReady();
122
+ (0, chai_1.expect)(fetchMock.callCount).to.equal(1);
123
+ (0, chai_1.expect)(await auth.getHttpHeaders()).to.deep.equal({
124
+ Authorization: "Bearer new-tok",
125
+ });
126
+ });
127
+ (0, mocha_1.it)("triggers refresh within the 30-second expiry buffer", async () => {
128
+ const almostExpired = Date.now() + 15000; // 15 s left
129
+ const fetchMock = (0, helpers_1.createFetchMock)(() => (0, helpers_1.createJsonResponse)({
130
+ access_token: "refreshed-tok",
131
+ expires_in: 3600,
132
+ }));
133
+ const auth = new doover_token_auth_1.DooverTokenAuth({
134
+ token: "old-tok",
135
+ tokenExpires: new Date(almostExpired),
136
+ refreshToken: "rt",
137
+ authServerUrl: "https://auth.example.com",
138
+ authServerClientId: "client-1",
139
+ fetchImpl: fetchMock,
140
+ });
141
+ await auth.ensureReady();
142
+ (0, chai_1.expect)(fetchMock.callCount).to.equal(1);
143
+ (0, chai_1.expect)(await auth.getHttpHeaders()).to.deep.equal({
144
+ Authorization: "Bearer refreshed-tok",
145
+ });
146
+ });
147
+ (0, mocha_1.it)("throws DooverAuthError when refresh metadata is incomplete", async () => {
148
+ const auth = new doover_token_auth_1.DooverTokenAuth({
149
+ token: null,
150
+ // No refreshToken, authServerUrl, or authServerClientId
151
+ });
152
+ await (0, chai_1.expect)(auth.ensureReady()).to.be.rejectedWith(errors_1.DooverAuthError, /missing authServerUrl/);
153
+ });
154
+ (0, mocha_1.it)("throws DooverAuthError when refresh HTTP request fails", async () => {
155
+ const fetchMock = (0, helpers_1.createFetchMock)(() => (0, helpers_1.createJsonResponse)({ error: "invalid_grant" }, { status: 400 }));
156
+ const auth = new doover_token_auth_1.DooverTokenAuth({
157
+ token: null,
158
+ refreshToken: "rt",
159
+ authServerUrl: "https://auth.example.com",
160
+ authServerClientId: "client-1",
161
+ fetchImpl: fetchMock,
162
+ });
163
+ await (0, chai_1.expect)(auth.ensureReady()).to.be.rejectedWith(errors_1.DooverAuthError, /status 400/);
164
+ });
165
+ (0, mocha_1.it)("writes refreshed token back to the attached ConfigManager", async () => {
166
+ const store = new MemoryProfileStore();
167
+ const profile = new auth_profile_1.AuthProfile({
168
+ profile: "test",
169
+ refreshToken: "rt",
170
+ authServerUrl: "https://auth.example.com",
171
+ authServerClientId: "client-1",
172
+ });
173
+ store.create(profile);
174
+ const fetchMock = (0, helpers_1.createFetchMock)(() => (0, helpers_1.createJsonResponse)({
175
+ access_token: "refreshed",
176
+ expires_in: 3600,
177
+ refresh_token: "new-rt",
178
+ }));
179
+ // Construct with no token so refresh is triggered
180
+ const auth = new doover_token_auth_1.DooverTokenAuth({
181
+ refreshToken: "rt",
182
+ authServerUrl: "https://auth.example.com",
183
+ authServerClientId: "client-1",
184
+ fetchImpl: fetchMock,
185
+ });
186
+ auth.attachProfile(profile, store);
187
+ await auth.ensureReady();
188
+ (0, chai_1.expect)(profile.token).to.equal("refreshed");
189
+ (0, chai_1.expect)(profile.refreshToken).to.equal("new-rt");
190
+ (0, chai_1.expect)(store.writeCalled).to.equal(1);
191
+ });
192
+ // -- WebSocket auth -------------------------------------------------------
193
+ (0, mocha_1.it)("prepareWebSocket uses Authorization header when canUseHeaders is true", async () => {
194
+ const auth = new doover_token_auth_1.DooverTokenAuth({ token: "ws-tok" });
195
+ const result = await auth.prepareWebSocket("wss://ws.example.com", true);
196
+ (0, chai_1.expect)(result.url).to.equal("wss://ws.example.com");
197
+ (0, chai_1.expect)(result.headers).to.deep.equal({
198
+ Authorization: "Bearer ws-tok",
199
+ });
200
+ });
201
+ (0, mocha_1.it)("prepareWebSocket appends ?token= when canUseHeaders is false", async () => {
202
+ const auth = new doover_token_auth_1.DooverTokenAuth({ token: "ws-tok" });
203
+ const result = await auth.prepareWebSocket("wss://ws.example.com/path", false);
204
+ (0, chai_1.expect)(result.url).to.equal("wss://ws.example.com/path?token=ws-tok");
205
+ (0, chai_1.expect)(result.headers).to.be.undefined;
206
+ });
207
+ });
208
+ // ---------------------------------------------------------------------------
209
+ // JWT expiry decoding
210
+ // ---------------------------------------------------------------------------
211
+ (0, mocha_1.describe)("JWT decodeTokenExpiry", () => {
212
+ (0, mocha_1.it)("decodes token exp claim", () => {
213
+ const exp = Math.floor(Date.now() / 1000) + 3600;
214
+ const token = makeJwt(exp);
215
+ const result = (0, jwt_1.decodeTokenExpiry)(token);
216
+ (0, chai_1.expect)(result).to.be.instanceOf(Date);
217
+ (0, chai_1.expect)(result.getTime()).to.equal(exp * 1000);
218
+ });
219
+ (0, mocha_1.it)("returns null for tokens without exp", () => {
220
+ const header = Buffer.from(JSON.stringify({ alg: "HS256" })).toString("base64url");
221
+ const payload = Buffer.from(JSON.stringify({ sub: "user" })).toString("base64url");
222
+ (0, chai_1.expect)((0, jwt_1.decodeTokenExpiry)(`${header}.${payload}.sig`)).to.equal(null);
223
+ });
224
+ (0, mocha_1.it)("returns null for malformed tokens", () => {
225
+ (0, chai_1.expect)((0, jwt_1.decodeTokenExpiry)("not-a-jwt")).to.equal(null);
226
+ (0, chai_1.expect)((0, jwt_1.decodeTokenExpiry)("")).to.equal(null);
227
+ });
228
+ });
229
+ // ---------------------------------------------------------------------------
230
+ // AuthProfile parse / dump
231
+ // ---------------------------------------------------------------------------
232
+ (0, mocha_1.describe)("AuthProfile", () => {
233
+ (0, mocha_1.it)("parses reduced-format config lines", () => {
234
+ const lines = [
235
+ "TOKEN=abc123",
236
+ "TOKEN_EXPIRES=2026-01-01T00:00:00.000Z",
237
+ "AGENT_ID=agent-1",
238
+ "BASE_URL=https://control.example.com",
239
+ "BASE_DATA_URL=https://data.example.com",
240
+ "REFRESH_TOKEN=rt-xyz",
241
+ "REFRESH_TOKEN_ID=rti-1",
242
+ "AUTH_SERVER_URL=https://auth.example.com",
243
+ "AUTH_SERVER_CLIENT_ID=client-1",
244
+ ];
245
+ const profile = auth_profile_1.AuthProfile.parse("prod", lines);
246
+ (0, chai_1.expect)(profile.profile).to.equal("prod");
247
+ (0, chai_1.expect)(profile.token).to.equal("abc123");
248
+ (0, chai_1.expect)(profile.tokenExpires).to.equal("2026-01-01T00:00:00.000Z");
249
+ (0, chai_1.expect)(profile.agentId).to.equal("agent-1");
250
+ (0, chai_1.expect)(profile.controlBaseUrl).to.equal("https://control.example.com");
251
+ (0, chai_1.expect)(profile.dataBaseUrl).to.equal("https://data.example.com");
252
+ (0, chai_1.expect)(profile.refreshToken).to.equal("rt-xyz");
253
+ (0, chai_1.expect)(profile.refreshTokenId).to.equal("rti-1");
254
+ (0, chai_1.expect)(profile.authServerUrl).to.equal("https://auth.example.com");
255
+ (0, chai_1.expect)(profile.authServerClientId).to.equal("client-1");
256
+ });
257
+ (0, mocha_1.it)("dumps back to the same reduced format", () => {
258
+ const profile = new auth_profile_1.AuthProfile({
259
+ profile: "test",
260
+ token: "tok",
261
+ agentId: "a1",
262
+ refreshToken: "rt",
263
+ });
264
+ const dumped = profile.dump();
265
+ (0, chai_1.expect)(dumped).to.include("TOKEN=tok");
266
+ (0, chai_1.expect)(dumped).to.include("AGENT_ID=a1");
267
+ (0, chai_1.expect)(dumped).to.include("REFRESH_TOKEN=rt");
268
+ // Null fields should be absent
269
+ (0, chai_1.expect)(dumped).not.to.include("TOKEN_EXPIRES");
270
+ (0, chai_1.expect)(dumped).not.to.include("BASE_URL");
271
+ });
272
+ (0, mocha_1.it)("dumpBlock includes the [profile=...] header", () => {
273
+ const profile = new auth_profile_1.AuthProfile({ profile: "dev", token: "t1" });
274
+ const block = profile.dumpBlock();
275
+ (0, chai_1.expect)(block).to.match(/^\[profile=dev\]/);
276
+ (0, chai_1.expect)(block).to.include("TOKEN=t1");
277
+ });
278
+ });
279
+ // ---------------------------------------------------------------------------
280
+ // buildAuth
281
+ // ---------------------------------------------------------------------------
282
+ (0, mocha_1.describe)("buildAuth", () => {
283
+ (0, mocha_1.it)("returns provided auth as-is", () => {
284
+ const auth = new cookie_auth_1.CookieAuth();
285
+ (0, chai_1.expect)((0, build_auth_1.buildAuth)({ auth })).to.equal(auth);
286
+ });
287
+ (0, mocha_1.it)("throws when auth is combined with raw auth config", () => {
288
+ const auth = new cookie_auth_1.CookieAuth();
289
+ (0, chai_1.expect)(() => (0, build_auth_1.buildAuth)({ auth, token: "tok" })).to.throw(errors_1.DooverAuthError, /Cannot combine/);
290
+ });
291
+ (0, mocha_1.it)("builds CookieAuth when no token-related input is present", () => {
292
+ (0, chai_1.expect)((0, build_auth_1.buildAuth)({})).to.be.instanceOf(cookie_auth_1.CookieAuth);
293
+ });
294
+ (0, mocha_1.it)("builds DooverTokenAuth when token is provided", () => {
295
+ const auth = (0, build_auth_1.buildAuth)({ token: "tok" });
296
+ (0, chai_1.expect)(auth).to.be.instanceOf(doover_token_auth_1.DooverTokenAuth);
297
+ });
298
+ (0, mocha_1.it)("builds DooverTokenAuth from an AuthProfile with token", () => {
299
+ const profile = new auth_profile_1.AuthProfile({ profile: "test", token: "t1" });
300
+ const auth = (0, build_auth_1.buildAuth)({ profile });
301
+ (0, chai_1.expect)(auth).to.be.instanceOf(doover_token_auth_1.DooverTokenAuth);
302
+ });
303
+ (0, mocha_1.it)("explicit raw fields override profile-loaded values", async () => {
304
+ const profile = new auth_profile_1.AuthProfile({ profile: "test", token: "profile-tok" });
305
+ const auth = (0, build_auth_1.buildAuth)({ profile, token: "override-tok" });
306
+ (0, chai_1.expect)(auth).to.be.instanceOf(doover_token_auth_1.DooverTokenAuth);
307
+ (0, chai_1.expect)(await auth.getHttpHeaders()).to.deep.equal({
308
+ Authorization: "Bearer override-tok",
309
+ });
310
+ });
311
+ (0, mocha_1.it)("resolves a string profile via configManager", () => {
312
+ const store = new MemoryProfileStore();
313
+ store.create(new auth_profile_1.AuthProfile({ profile: "staging", token: "st" }));
314
+ const auth = (0, build_auth_1.buildAuth)({ profile: "staging", configManager: store });
315
+ (0, chai_1.expect)(auth).to.be.instanceOf(doover_token_auth_1.DooverTokenAuth);
316
+ });
317
+ (0, mocha_1.it)("throws when profile is a string without configManager", () => {
318
+ (0, chai_1.expect)(() => (0, build_auth_1.buildAuth)({ profile: "staging" })).to.throw(errors_1.DooverAuthError, /configManager/);
319
+ });
320
+ (0, mocha_1.it)("throws when string profile is not found in configManager", () => {
321
+ const store = new MemoryProfileStore();
322
+ (0, chai_1.expect)(() => (0, build_auth_1.buildAuth)({ profile: "nope", configManager: store })).to.throw(errors_1.DooverAuthError, /not found/);
323
+ });
324
+ });
325
+ // ---------------------------------------------------------------------------
326
+ // DooverClient shared auth instance
327
+ // ---------------------------------------------------------------------------
328
+ (0, mocha_1.describe)("DooverClient auth sharing", () => {
329
+ (0, mocha_1.beforeEach)(() => {
330
+ (0, helpers_1.installSessionStorageMock)();
331
+ helpers_1.MockWebSocket.reset();
332
+ Object.defineProperty(globalThis, "WebSocket", {
333
+ value: helpers_1.MockWebSocket,
334
+ configurable: true,
335
+ writable: true,
336
+ });
337
+ });
338
+ (0, mocha_1.it)("shares one auth instance across REST and gateway", () => {
339
+ const client = new doover_client_1.DooverClient({
340
+ dataRestUrl: "https://api.example.com",
341
+ controlApiUrl: "https://control.example.com",
342
+ dataWssUrl: "wss://ws.example.com",
343
+ token: "shared-tok",
344
+ fetchImpl: (0, helpers_1.createFetchMock)(),
345
+ webSocketImpl: helpers_1.MockWebSocket,
346
+ });
347
+ (0, chai_1.expect)(client.auth).to.be.instanceOf(doover_token_auth_1.DooverTokenAuth);
348
+ (0, chai_1.expect)(client.rest.auth).to.equal(client.auth);
349
+ // viewer's internal rest and gateway should also share the same auth
350
+ (0, chai_1.expect)(client.viewer.rest.auth).to.equal(client.auth);
351
+ });
352
+ (0, mocha_1.it)("defaults to CookieAuth when no auth inputs are provided", () => {
353
+ const client = new doover_client_1.DooverClient({
354
+ dataRestUrl: "https://api.example.com",
355
+ controlApiUrl: "https://control.example.com",
356
+ dataWssUrl: "wss://ws.example.com",
357
+ fetchImpl: (0, helpers_1.createFetchMock)(),
358
+ webSocketImpl: helpers_1.MockWebSocket,
359
+ });
360
+ (0, chai_1.expect)(client.auth).to.be.instanceOf(cookie_auth_1.CookieAuth);
361
+ });
362
+ });
363
+ // ---------------------------------------------------------------------------
364
+ // GatewayClient auth integration
365
+ // ---------------------------------------------------------------------------
366
+ (0, mocha_1.describe)("GatewayClient auth integration", () => {
367
+ (0, mocha_1.beforeEach)(() => {
368
+ helpers_1.MockWebSocket.reset();
369
+ Object.defineProperty(globalThis, "WebSocket", {
370
+ value: helpers_1.MockWebSocket,
371
+ configurable: true,
372
+ writable: true,
373
+ });
374
+ });
375
+ (0, mocha_1.afterEach)(() => sinon_1.default.restore());
376
+ (0, mocha_1.it)("appends ?token= when using token auth without webSocketFactory", async () => {
377
+ const auth = new doover_token_auth_1.DooverTokenAuth({ token: "gw-tok" });
378
+ const gw = new gateway_client_1.GatewayClient({
379
+ dataRestUrl: "https://api.example.com",
380
+ controlApiUrl: "https://control.example.com",
381
+ dataWssUrl: "wss://ws.example.com",
382
+ webSocketImpl: helpers_1.MockWebSocket,
383
+ }, auth);
384
+ await gw.connect();
385
+ const ws = helpers_1.MockWebSocket.instances[0];
386
+ (0, chai_1.expect)(ws.url).to.equal("wss://ws.example.com/?token=gw-tok");
387
+ });
388
+ (0, mocha_1.it)("uses webSocketFactory with Authorization header for token auth", async () => {
389
+ const auth = new doover_token_auth_1.DooverTokenAuth({ token: "factory-tok" });
390
+ let capturedHeaders;
391
+ const factory = (opts) => {
392
+ capturedHeaders = opts.headers;
393
+ return new helpers_1.MockWebSocket(opts.url);
394
+ };
395
+ const gw = new gateway_client_1.GatewayClient({
396
+ dataRestUrl: "https://api.example.com",
397
+ controlApiUrl: "https://control.example.com",
398
+ dataWssUrl: "wss://ws.example.com",
399
+ webSocketFactory: factory,
400
+ }, auth);
401
+ await gw.connect();
402
+ (0, chai_1.expect)(capturedHeaders).to.deep.equal({
403
+ Authorization: "Bearer factory-tok",
404
+ });
405
+ // URL should NOT have the token query param
406
+ const ws = helpers_1.MockWebSocket.instances[0];
407
+ (0, chai_1.expect)(ws.url).to.equal("wss://ws.example.com");
408
+ });
409
+ (0, mocha_1.it)("cookie auth leaves websocket URL unchanged", async () => {
410
+ const auth = new cookie_auth_1.CookieAuth();
411
+ const gw = new gateway_client_1.GatewayClient({
412
+ dataRestUrl: "https://api.example.com",
413
+ controlApiUrl: "https://control.example.com",
414
+ dataWssUrl: "wss://ws.example.com",
415
+ webSocketImpl: helpers_1.MockWebSocket,
416
+ }, auth);
417
+ await gw.connect();
418
+ const ws = helpers_1.MockWebSocket.instances[0];
419
+ (0, chai_1.expect)(ws.url).to.equal("wss://ws.example.com");
420
+ });
421
+ (0, mocha_1.it)("reconnect uses newly refreshed token", async () => {
422
+ let callCount = 0;
423
+ const fetchMock = (0, helpers_1.createFetchMock)(() => {
424
+ callCount++;
425
+ return (0, helpers_1.createJsonResponse)({
426
+ access_token: `refreshed-${callCount}`,
427
+ expires_in: 3600,
428
+ });
429
+ });
430
+ const auth = new doover_token_auth_1.DooverTokenAuth({
431
+ token: null,
432
+ refreshToken: "rt",
433
+ authServerUrl: "https://auth.example.com",
434
+ authServerClientId: "c1",
435
+ fetchImpl: fetchMock,
436
+ });
437
+ const gw = new gateway_client_1.GatewayClient({
438
+ dataRestUrl: "https://api.example.com",
439
+ controlApiUrl: "https://control.example.com",
440
+ dataWssUrl: "wss://ws.example.com",
441
+ webSocketImpl: helpers_1.MockWebSocket,
442
+ }, auth);
443
+ await gw.connect();
444
+ const ws1 = helpers_1.MockWebSocket.instances[0];
445
+ (0, chai_1.expect)(ws1.url).to.include("token=refreshed-1");
446
+ // Simulate disconnect + reconnect
447
+ gw.disconnect();
448
+ // Clear the token to force a re-refresh
449
+ auth.setToken(null);
450
+ await gw.connect();
451
+ const ws2 = helpers_1.MockWebSocket.instances[1];
452
+ (0, chai_1.expect)(ws2.url).to.include("token=refreshed-2");
453
+ });
454
+ });
455
+ // ---------------------------------------------------------------------------
456
+ // RestClient auth integration
457
+ // ---------------------------------------------------------------------------
458
+ (0, mocha_1.describe)("RestClient auth integration", () => {
459
+ (0, mocha_1.beforeEach)(() => (0, helpers_1.installSessionStorageMock)());
460
+ (0, mocha_1.it)("sends Authorization header with token auth", async () => {
461
+ const auth = new doover_token_auth_1.DooverTokenAuth({ token: "rest-tok" });
462
+ const fetchMock = (0, helpers_1.createFetchMock)();
463
+ const client = new rest_client_1.RestClient({
464
+ dataRestUrl: "https://api.example.com",
465
+ controlApiUrl: "https://control.example.com",
466
+ dataWssUrl: "wss://ws.example.com",
467
+ fetchImpl: fetchMock,
468
+ }, auth);
469
+ await client.get("/items");
470
+ const [, init] = fetchMock.getCall(0).args;
471
+ const headers = init?.headers;
472
+ (0, chai_1.expect)(headers.get("Authorization")).to.equal("Bearer rest-tok");
473
+ (0, chai_1.expect)(init?.credentials).to.equal("omit");
474
+ });
475
+ (0, mocha_1.it)("uses credentials: include with cookie auth", async () => {
476
+ const auth = new cookie_auth_1.CookieAuth();
477
+ const fetchMock = (0, helpers_1.createFetchMock)();
478
+ const client = new rest_client_1.RestClient({
479
+ dataRestUrl: "https://api.example.com",
480
+ controlApiUrl: "https://control.example.com",
481
+ dataWssUrl: "wss://ws.example.com",
482
+ fetchImpl: fetchMock,
483
+ }, auth);
484
+ await client.get("/items");
485
+ const [, init] = fetchMock.getCall(0).args;
486
+ const headers = init?.headers;
487
+ (0, chai_1.expect)(headers.get("Authorization")).to.equal(null);
488
+ (0, chai_1.expect)(init?.credentials).to.equal("include");
489
+ });
490
+ (0, mocha_1.it)("uses credentials: include when no auth is provided (backward compat)", async () => {
491
+ const fetchMock = (0, helpers_1.createFetchMock)();
492
+ const client = new rest_client_1.RestClient({
493
+ dataRestUrl: "https://api.example.com",
494
+ controlApiUrl: "https://control.example.com",
495
+ dataWssUrl: "wss://ws.example.com",
496
+ fetchImpl: fetchMock,
497
+ });
498
+ await client.get("/items");
499
+ const [, init] = fetchMock.getCall(0).args;
500
+ (0, chai_1.expect)(init?.credentials).to.equal("include");
501
+ });
502
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,160 @@
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const chai_1 = require("chai");
37
+ const mocha_1 = require("mocha");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const os = __importStar(require("os"));
41
+ const config_manager_1 = require("../node/config-manager");
42
+ const auth_profile_1 = require("../auth/auth-profile");
43
+ function tmpConfigPath() {
44
+ return path.join(os.tmpdir(), `doover-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, "config");
45
+ }
46
+ function cleanup(configPath) {
47
+ try {
48
+ fs.unlinkSync(configPath);
49
+ }
50
+ catch { /* ignore */ }
51
+ try {
52
+ fs.rmdirSync(path.dirname(configPath));
53
+ }
54
+ catch { /* ignore */ }
55
+ }
56
+ (0, mocha_1.describe)("ConfigManager", () => {
57
+ const paths = [];
58
+ (0, mocha_1.afterEach)(() => {
59
+ for (const p of paths) {
60
+ cleanup(p);
61
+ }
62
+ paths.length = 0;
63
+ });
64
+ (0, mocha_1.it)("starts empty when config file does not exist", () => {
65
+ const p = tmpConfigPath();
66
+ paths.push(p);
67
+ const mgr = new config_manager_1.ConfigManager(p);
68
+ (0, chai_1.expect)(mgr.currentProfile).to.equal(null);
69
+ (0, chai_1.expect)(mgr.current).to.equal(null);
70
+ (0, chai_1.expect)(mgr.get("any")).to.be.undefined;
71
+ });
72
+ (0, mocha_1.it)("parses a reduced-format Doover2 config file", () => {
73
+ const p = tmpConfigPath();
74
+ paths.push(p);
75
+ fs.mkdirSync(path.dirname(p), { recursive: true });
76
+ fs.writeFileSync(p, [
77
+ "[profile=prod]",
78
+ "TOKEN=abc123",
79
+ "AGENT_ID=agent-1",
80
+ "REFRESH_TOKEN=rt-xyz",
81
+ "AUTH_SERVER_URL=https://auth.example.com",
82
+ "AUTH_SERVER_CLIENT_ID=client-1",
83
+ "",
84
+ "[profile=staging]",
85
+ "TOKEN=stg-tok",
86
+ ].join("\n"), "utf-8");
87
+ const mgr = new config_manager_1.ConfigManager(p);
88
+ (0, chai_1.expect)(mgr.currentProfile).to.equal("prod");
89
+ const prod = mgr.get("prod");
90
+ (0, chai_1.expect)(prod).to.not.be.undefined;
91
+ (0, chai_1.expect)(prod.token).to.equal("abc123");
92
+ (0, chai_1.expect)(prod.agentId).to.equal("agent-1");
93
+ (0, chai_1.expect)(prod.refreshToken).to.equal("rt-xyz");
94
+ const staging = mgr.get("staging");
95
+ (0, chai_1.expect)(staging).to.not.be.undefined;
96
+ (0, chai_1.expect)(staging.token).to.equal("stg-tok");
97
+ });
98
+ (0, mocha_1.it)("preserves legacy Doover1 blocks verbatim", () => {
99
+ const p = tmpConfigPath();
100
+ paths.push(p);
101
+ fs.mkdirSync(path.dirname(p), { recursive: true });
102
+ const legacyBlock = "[legacy-section]\nSOME_KEY=some_value\nOTHER=123";
103
+ fs.writeFileSync(p, `${legacyBlock}\n\n[profile=prod]\nTOKEN=t1\n`, "utf-8");
104
+ const mgr = new config_manager_1.ConfigManager(p);
105
+ mgr.write();
106
+ const written = fs.readFileSync(p, "utf-8");
107
+ // The legacy block should appear verbatim before the managed profile
108
+ (0, chai_1.expect)(written).to.include("[legacy-section]\nSOME_KEY=some_value\nOTHER=123");
109
+ (0, chai_1.expect)(written).to.include("[profile=prod]\nTOKEN=t1");
110
+ });
111
+ (0, mocha_1.it)("rewrites legacy Doover2 blocks back in reduced format", () => {
112
+ const p = tmpConfigPath();
113
+ paths.push(p);
114
+ fs.mkdirSync(path.dirname(p), { recursive: true });
115
+ fs.writeFileSync(p, "[profile=test]\nTOKEN=old\nAGENT_ID=a1\n", "utf-8");
116
+ const mgr = new config_manager_1.ConfigManager(p);
117
+ // Modify the profile
118
+ const profile = mgr.get("test");
119
+ profile.token = "new-token";
120
+ mgr.create(profile);
121
+ mgr.write();
122
+ const written = fs.readFileSync(p, "utf-8");
123
+ (0, chai_1.expect)(written).to.include("TOKEN=new-token");
124
+ (0, chai_1.expect)(written).to.include("AGENT_ID=a1");
125
+ });
126
+ (0, mocha_1.it)("write() creates the .doover directory if missing", () => {
127
+ const p = tmpConfigPath();
128
+ paths.push(p);
129
+ const mgr = new config_manager_1.ConfigManager(p);
130
+ const profile = new auth_profile_1.AuthProfile({ profile: "test", token: "t" });
131
+ mgr.create(profile);
132
+ mgr.write();
133
+ (0, chai_1.expect)(fs.existsSync(p)).to.equal(true);
134
+ const content = fs.readFileSync(p, "utf-8");
135
+ (0, chai_1.expect)(content).to.include("[profile=test]");
136
+ (0, chai_1.expect)(content).to.include("TOKEN=t");
137
+ });
138
+ (0, mocha_1.it)("delete removes a managed profile", () => {
139
+ const p = tmpConfigPath();
140
+ paths.push(p);
141
+ fs.mkdirSync(path.dirname(p), { recursive: true });
142
+ fs.writeFileSync(p, "[profile=a]\nTOKEN=1\n\n[profile=b]\nTOKEN=2\n", "utf-8");
143
+ const mgr = new config_manager_1.ConfigManager(p);
144
+ (0, chai_1.expect)(mgr.get("a")).to.not.be.undefined;
145
+ mgr.delete("a");
146
+ (0, chai_1.expect)(mgr.get("a")).to.be.undefined;
147
+ mgr.write();
148
+ const written = fs.readFileSync(p, "utf-8");
149
+ (0, chai_1.expect)(written).not.to.include("[profile=a]");
150
+ (0, chai_1.expect)(written).to.include("[profile=b]");
151
+ });
152
+ });
153
+ (0, mocha_1.describe)("doover-js/node exports", () => {
154
+ (0, mocha_1.it)("exports ConfigManager from the node subpath", () => {
155
+ // Verify the node subpath module re-exports ConfigManager
156
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
157
+ const nodeExports = require("../node/index");
158
+ (0, chai_1.expect)(nodeExports.ConfigManager).to.equal(config_manager_1.ConfigManager);
159
+ });
160
+ });