doover-js 0.4.7 → 0.5.0-alpha.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 +31 -0
- package/dist/apis/_args.d.ts +16 -0
- package/dist/apis/_args.js +26 -0
- package/dist/apis/agents-api.d.ts +4 -1
- package/dist/apis/agents-api.js +139 -1
- package/dist/apis/aggregates-api.d.ts +23 -3
- package/dist/apis/aggregates-api.js +33 -4
- package/dist/apis/alarms-api.d.ts +30 -0
- package/dist/apis/alarms-api.js +51 -6
- package/dist/apis/channels-api.d.ts +40 -3
- package/dist/apis/channels-api.js +50 -5
- package/dist/apis/connections-api.d.ts +21 -5
- package/dist/apis/connections-api.js +26 -10
- package/dist/apis/messages-api.d.ts +55 -9
- package/dist/apis/messages-api.js +96 -11
- package/dist/apis/notifications-api.d.ts +52 -0
- package/dist/apis/notifications-api.js +99 -13
- package/dist/apis/permissions-api.d.ts +8 -0
- package/dist/apis/permissions-api.js +11 -2
- package/dist/apis/processors-api.d.ts +35 -0
- package/dist/apis/processors-api.js +64 -8
- package/dist/apis/users-api.d.ts +8 -0
- package/dist/apis/users-api.js +13 -0
- package/dist/client/doover-client.d.ts +4 -8
- package/dist/client/doover-client.js +16 -28
- package/dist/client/stats.d.ts +24 -0
- package/dist/client/stats.js +62 -0
- package/dist/gateway/gateway-client.d.ts +13 -1
- package/dist/gateway/gateway-client.js +54 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +13 -7
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +4 -1
- package/dist/react/useChannelAggregate.js +8 -1
- package/dist/react/useChannelMessages.js +5 -2
- package/dist/react/useChannelSubscription.js +8 -18
- package/dist/react/useInvocationLogs.d.ts +23 -0
- package/dist/react/useInvocationLogs.js +41 -0
- package/dist/react/useMultiAgentAggregates.js +8 -14
- package/dist/react/useMultiAgentChannelMessages.js +8 -13
- package/dist/react/useSendMessage.js +6 -1
- package/dist/react/useSendRpc.js +4 -1
- package/dist/react/useUpdateAggregate.js +9 -3
- package/dist/rpc/errors.d.ts +10 -0
- package/dist/rpc/errors.js +23 -0
- package/dist/rpc/rpc-dispatcher.d.ts +28 -0
- package/dist/rpc/rpc-dispatcher.js +129 -0
- package/dist/test/api-args.test.d.ts +1 -0
- package/dist/test/api-args.test.js +48 -0
- package/dist/test/api-overloads.test.d.ts +9 -0
- package/dist/test/api-overloads.test.js +276 -0
- package/dist/test/gateway-subscriptions.test.d.ts +1 -0
- package/dist/test/gateway-subscriptions.test.js +95 -0
- package/dist/test/react.test.js +36 -0
- package/dist/test/rpc-dispatcher.test.d.ts +1 -0
- package/dist/test/rpc-dispatcher.test.js +190 -0
- package/dist/test/stats-rpc.test.d.ts +1 -0
- package/dist/test/stats-rpc.test.js +69 -0
- package/dist/test/users-api.test.d.ts +1 -0
- package/dist/test/users-api.test.js +19 -0
- package/dist/viewer/doover-data-provider.d.ts +30 -34
- package/dist/viewer/doover-data-provider.js +82 -316
- package/package.json +1 -1
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeRestStub = makeRestStub;
|
|
4
|
+
const chai_1 = require("chai");
|
|
5
|
+
const channels_api_1 = require("../apis/channels-api");
|
|
6
|
+
const messages_api_1 = require("../apis/messages-api");
|
|
7
|
+
function makeRestStub() {
|
|
8
|
+
const calls = [];
|
|
9
|
+
const stub = {
|
|
10
|
+
calls,
|
|
11
|
+
get: (...args) => {
|
|
12
|
+
calls.push({ method: "get", args });
|
|
13
|
+
return Promise.resolve([]);
|
|
14
|
+
},
|
|
15
|
+
post: (...args) => {
|
|
16
|
+
calls.push({ method: "post", args });
|
|
17
|
+
return Promise.resolve(undefined);
|
|
18
|
+
},
|
|
19
|
+
put: (...args) => {
|
|
20
|
+
calls.push({ method: "put", args });
|
|
21
|
+
return Promise.resolve(undefined);
|
|
22
|
+
},
|
|
23
|
+
patch: (...args) => {
|
|
24
|
+
calls.push({ method: "patch", args });
|
|
25
|
+
return Promise.resolve(undefined);
|
|
26
|
+
},
|
|
27
|
+
delete: (...args) => {
|
|
28
|
+
calls.push({ method: "delete", args });
|
|
29
|
+
return Promise.resolve(undefined);
|
|
30
|
+
},
|
|
31
|
+
request: (...args) => {
|
|
32
|
+
calls.push({ method: "request", args });
|
|
33
|
+
return Promise.resolve(undefined);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
return stub;
|
|
37
|
+
}
|
|
38
|
+
describe("ChannelsApi overloads", () => {
|
|
39
|
+
it("getChannel positional and identifier-object produce identical requests", async () => {
|
|
40
|
+
const restA = makeRestStub();
|
|
41
|
+
const restB = makeRestStub();
|
|
42
|
+
const apiA = new channels_api_1.ChannelsApi(restA);
|
|
43
|
+
const apiB = new channels_api_1.ChannelsApi(restB);
|
|
44
|
+
await apiA.getChannel("a1", "c1", { include_aggregate: true });
|
|
45
|
+
await apiB.getChannel({ agentId: "a1", channelName: "c1" }, { include_aggregate: true });
|
|
46
|
+
(0, chai_1.expect)(restA.calls).to.deep.equal(restB.calls);
|
|
47
|
+
});
|
|
48
|
+
it("listChannels positional and identifier-object produce identical requests", async () => {
|
|
49
|
+
const restA = makeRestStub();
|
|
50
|
+
const restB = makeRestStub();
|
|
51
|
+
const apiA = new channels_api_1.ChannelsApi(restA);
|
|
52
|
+
const apiB = new channels_api_1.ChannelsApi(restB);
|
|
53
|
+
await apiA.listChannels("a1", { include_archived: true });
|
|
54
|
+
await apiB.listChannels({ agentId: "a1" }, { include_archived: true });
|
|
55
|
+
(0, chai_1.expect)(restA.calls).to.deep.equal(restB.calls);
|
|
56
|
+
});
|
|
57
|
+
it("archiveChannel posts to /archive", async () => {
|
|
58
|
+
const rest = makeRestStub();
|
|
59
|
+
const api = new channels_api_1.ChannelsApi(rest);
|
|
60
|
+
await api.archiveChannel("a1", "c1");
|
|
61
|
+
(0, chai_1.expect)(rest.calls).to.deep.equal([
|
|
62
|
+
{ method: "post", args: ["/agents/a1/channels/c1/archive", {}] },
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
it("unarchiveChannel posts to /unarchive", async () => {
|
|
66
|
+
const rest = makeRestStub();
|
|
67
|
+
const api = new channels_api_1.ChannelsApi(rest);
|
|
68
|
+
await api.unarchiveChannel({ agentId: "a1", channelName: "c1" });
|
|
69
|
+
(0, chai_1.expect)(rest.calls).to.deep.equal([
|
|
70
|
+
{ method: "post", args: ["/agents/a1/channels/c1/unarchive", {}] },
|
|
71
|
+
]);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe("MessagesApi overloads + defaults", () => {
|
|
75
|
+
it("listMessages positional and identifier-object produce identical requests", async () => {
|
|
76
|
+
const restA = makeRestStub();
|
|
77
|
+
const restB = makeRestStub();
|
|
78
|
+
const apiA = new messages_api_1.MessagesApi(restA);
|
|
79
|
+
const apiB = new messages_api_1.MessagesApi(restB);
|
|
80
|
+
await apiA.listMessages("a1", "c1", { limit: 5, order: "desc" });
|
|
81
|
+
await apiB.listMessages({ agentId: "a1", channelName: "c1" }, { limit: 5, order: "desc" });
|
|
82
|
+
(0, chai_1.expect)(restA.calls).to.deep.equal(restB.calls);
|
|
83
|
+
});
|
|
84
|
+
it("listMessages defaults limit to 10 when omitted", async () => {
|
|
85
|
+
const rest = makeRestStub();
|
|
86
|
+
const api = new messages_api_1.MessagesApi(rest);
|
|
87
|
+
await api.listMessages("a1", "c1");
|
|
88
|
+
(0, chai_1.expect)(rest.calls).to.have.lengthOf(1);
|
|
89
|
+
const params = rest.calls[0].args[1];
|
|
90
|
+
(0, chai_1.expect)(params.limit).to.equal(10);
|
|
91
|
+
(0, chai_1.expect)(params.before, "before should be set to a snowflake id").to.be.a("string");
|
|
92
|
+
});
|
|
93
|
+
it("listMessages with order='asc' reverses the response array", async () => {
|
|
94
|
+
const rest = makeRestStub();
|
|
95
|
+
rest.get = ((..._args) => Promise.resolve([
|
|
96
|
+
{ id: "3", data: {}, attachments: [], author_id: "a", channel: { agent_id: "a1", name: "c1" } },
|
|
97
|
+
{ id: "2", data: {}, attachments: [], author_id: "a", channel: { agent_id: "a1", name: "c1" } },
|
|
98
|
+
{ id: "1", data: {}, attachments: [], author_id: "a", channel: { agent_id: "a1", name: "c1" } },
|
|
99
|
+
]));
|
|
100
|
+
const api = new messages_api_1.MessagesApi(rest);
|
|
101
|
+
const result = await api.listMessages("a1", "c1", { order: "asc", limit: 3 });
|
|
102
|
+
(0, chai_1.expect)(result.map((m) => m.id)).to.deep.equal(["1", "2", "3"]);
|
|
103
|
+
});
|
|
104
|
+
it("listMessages with order='desc' (default) preserves server order", async () => {
|
|
105
|
+
const rest = makeRestStub();
|
|
106
|
+
rest.get = ((..._args) => Promise.resolve([
|
|
107
|
+
{ id: "3", data: {}, attachments: [], author_id: "a", channel: { agent_id: "a1", name: "c1" } },
|
|
108
|
+
{ id: "2", data: {}, attachments: [], author_id: "a", channel: { agent_id: "a1", name: "c1" } },
|
|
109
|
+
]));
|
|
110
|
+
const api = new messages_api_1.MessagesApi(rest);
|
|
111
|
+
const result = await api.listMessages("a1", "c1", { limit: 2 });
|
|
112
|
+
(0, chai_1.expect)(result.map((m) => m.id)).to.deep.equal(["3", "2"]);
|
|
113
|
+
});
|
|
114
|
+
it("getTimeseries strips order before sending to server", async () => {
|
|
115
|
+
const rest = makeRestStub();
|
|
116
|
+
const api = new messages_api_1.MessagesApi(rest);
|
|
117
|
+
await api.getTimeseries("a1", "c1", { limit: 5, order: "asc" });
|
|
118
|
+
(0, chai_1.expect)(rest.calls).to.have.lengthOf(1);
|
|
119
|
+
const wireParams = rest.calls[0].args[1];
|
|
120
|
+
(0, chai_1.expect)(wireParams.order).to.equal(undefined);
|
|
121
|
+
(0, chai_1.expect)(wireParams.limit).to.equal(5);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
const aggregates_api_1 = require("../apis/aggregates-api");
|
|
125
|
+
describe("AggregatesApi overloads", () => {
|
|
126
|
+
it("getAggregate positional and identifier-object produce identical requests", async () => {
|
|
127
|
+
const restA = makeRestStub();
|
|
128
|
+
const restB = makeRestStub();
|
|
129
|
+
const apiA = new aggregates_api_1.AggregatesApi(restA);
|
|
130
|
+
const apiB = new aggregates_api_1.AggregatesApi(restB);
|
|
131
|
+
await apiA.getAggregate("a1", "c1");
|
|
132
|
+
await apiB.getAggregate({ agentId: "a1", channelName: "c1" });
|
|
133
|
+
(0, chai_1.expect)(restA.calls).to.deep.equal(restB.calls);
|
|
134
|
+
});
|
|
135
|
+
it("patchAggregate identifier form passes through body and params", async () => {
|
|
136
|
+
const rest = makeRestStub();
|
|
137
|
+
const api = new aggregates_api_1.AggregatesApi(rest);
|
|
138
|
+
await api.patchAggregate({ agentId: "a1", channelName: "c1" }, { x: 1 }, { suppress_response: true });
|
|
139
|
+
(0, chai_1.expect)(rest.calls).to.deep.equal([
|
|
140
|
+
{
|
|
141
|
+
method: "patch",
|
|
142
|
+
args: ["/agents/a1/channels/c1/aggregate", { x: 1 }, { suppress_response: true }],
|
|
143
|
+
},
|
|
144
|
+
]);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
const alarms_api_1 = require("../apis/alarms-api");
|
|
148
|
+
describe("AlarmsApi overloads", () => {
|
|
149
|
+
it("listAlarms positional and identifier-object produce identical requests", async () => {
|
|
150
|
+
const restA = makeRestStub();
|
|
151
|
+
const restB = makeRestStub();
|
|
152
|
+
const apiA = new alarms_api_1.AlarmsApi(restA);
|
|
153
|
+
const apiB = new alarms_api_1.AlarmsApi(restB);
|
|
154
|
+
await apiA.listAlarms("a1", "c1");
|
|
155
|
+
await apiB.listAlarms({ agentId: "a1", channelName: "c1" });
|
|
156
|
+
(0, chai_1.expect)(restA.calls).to.deep.equal(restB.calls);
|
|
157
|
+
});
|
|
158
|
+
it("getAlarm via identifier hits expected path", async () => {
|
|
159
|
+
const rest = makeRestStub();
|
|
160
|
+
const api = new alarms_api_1.AlarmsApi(rest);
|
|
161
|
+
await api.getAlarm({ agentId: "a1", channelName: "c1" }, "alm1");
|
|
162
|
+
(0, chai_1.expect)(rest.calls).to.deep.equal([
|
|
163
|
+
{ method: "get", args: ["/agents/a1/channels/c1/alarms/alm1"] },
|
|
164
|
+
]);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
const connections_api_1 = require("../apis/connections-api");
|
|
168
|
+
describe("ConnectionsApi overloads", () => {
|
|
169
|
+
it("getAgentConnections positional and identifier-object produce identical requests", async () => {
|
|
170
|
+
const restA = makeRestStub();
|
|
171
|
+
const restB = makeRestStub();
|
|
172
|
+
const apiA = new connections_api_1.ConnectionsApi(restA);
|
|
173
|
+
const apiB = new connections_api_1.ConnectionsApi(restB);
|
|
174
|
+
await apiA.getAgentConnections("a1");
|
|
175
|
+
await apiB.getAgentConnections({ agentId: "a1" });
|
|
176
|
+
(0, chai_1.expect)(restA.calls).to.deep.equal(restB.calls);
|
|
177
|
+
});
|
|
178
|
+
it("getChannelSubscriptions identifier form hits expected path", async () => {
|
|
179
|
+
const rest = makeRestStub();
|
|
180
|
+
const api = new connections_api_1.ConnectionsApi(rest);
|
|
181
|
+
await api.getChannelSubscriptions({ agentId: "a1", channelName: "c1" });
|
|
182
|
+
(0, chai_1.expect)(rest.calls).to.deep.equal([
|
|
183
|
+
{ method: "get", args: ["/agents/a1/channels/c1/subscriptions"] },
|
|
184
|
+
]);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
const notifications_api_1 = require("../apis/notifications-api");
|
|
188
|
+
describe("NotificationsApi overloads", () => {
|
|
189
|
+
it("getAgentNotifications positional and identifier-object produce identical requests", async () => {
|
|
190
|
+
const restA = makeRestStub();
|
|
191
|
+
const restB = makeRestStub();
|
|
192
|
+
const apiA = new notifications_api_1.NotificationsApi(restA);
|
|
193
|
+
const apiB = new notifications_api_1.NotificationsApi(restB);
|
|
194
|
+
await apiA.getAgentNotifications("a1");
|
|
195
|
+
await apiB.getAgentNotifications({ agentId: "a1" });
|
|
196
|
+
(0, chai_1.expect)(restA.calls).to.deep.equal(restB.calls);
|
|
197
|
+
});
|
|
198
|
+
it("updateNotificationEndpoint identifier form preserves endpointId and body", async () => {
|
|
199
|
+
const rest = makeRestStub();
|
|
200
|
+
const api = new notifications_api_1.NotificationsApi(rest);
|
|
201
|
+
await api.updateNotificationEndpoint({ agentId: "a1" }, "ep1", { name: "x" });
|
|
202
|
+
(0, chai_1.expect)(rest.calls).to.deep.equal([
|
|
203
|
+
{
|
|
204
|
+
method: "patch",
|
|
205
|
+
args: ["/agents/a1/notifications/endpoints/ep1", { name: "x" }],
|
|
206
|
+
},
|
|
207
|
+
]);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
const permissions_api_1 = require("../apis/permissions-api");
|
|
211
|
+
describe("PermissionsApi overloads", () => {
|
|
212
|
+
it("getAgentPermission positional and identifier-object produce identical requests", async () => {
|
|
213
|
+
const restA = makeRestStub();
|
|
214
|
+
const restB = makeRestStub();
|
|
215
|
+
const apiA = new permissions_api_1.PermissionsApi(restA);
|
|
216
|
+
const apiB = new permissions_api_1.PermissionsApi(restB);
|
|
217
|
+
await apiA.getAgentPermission("a1");
|
|
218
|
+
await apiB.getAgentPermission({ agentId: "a1" });
|
|
219
|
+
(0, chai_1.expect)(restA.calls).to.deep.equal(restB.calls);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
const processors_api_1 = require("../apis/processors-api");
|
|
223
|
+
describe("ProcessorsApi overloads", () => {
|
|
224
|
+
it("createProcessorSchedule identifier form hits expected path", async () => {
|
|
225
|
+
const rest = makeRestStub();
|
|
226
|
+
const api = new processors_api_1.ProcessorsApi(rest);
|
|
227
|
+
await api.createProcessorSchedule({ agentId: "a1" }, "sch1", { foo: "bar" });
|
|
228
|
+
(0, chai_1.expect)(rest.calls).to.deep.equal([
|
|
229
|
+
{
|
|
230
|
+
method: "put",
|
|
231
|
+
args: ["/agents/a1/processors/schedules/sch1", { foo: "bar" }],
|
|
232
|
+
},
|
|
233
|
+
]);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
const agents_api_1 = require("../apis/agents-api");
|
|
237
|
+
describe("AgentsApi listAgents", () => {
|
|
238
|
+
it("calls /agents/ on controlApiUrl with omitSharingHeader", async () => {
|
|
239
|
+
const rest = makeRestStub();
|
|
240
|
+
const api = new agents_api_1.AgentsApi(rest, "https://control.example.com");
|
|
241
|
+
rest.request = ((...args) => {
|
|
242
|
+
rest.calls.push({ method: "request", args });
|
|
243
|
+
return Promise.resolve({ agents: [] });
|
|
244
|
+
});
|
|
245
|
+
await api.listAgents({ includeArchived: true });
|
|
246
|
+
(0, chai_1.expect)(rest.calls).to.have.lengthOf(1);
|
|
247
|
+
(0, chai_1.expect)(rest.calls[0].method).to.equal("request");
|
|
248
|
+
const req = rest.calls[0].args[0];
|
|
249
|
+
(0, chai_1.expect)(req.path).to.equal("/agents/");
|
|
250
|
+
(0, chai_1.expect)(req.baseUrl).to.equal("https://control.example.com");
|
|
251
|
+
(0, chai_1.expect)(req.omitSharingHeader).to.equal(true);
|
|
252
|
+
(0, chai_1.expect)(req.query).to.deep.equal({ "include-archived": true });
|
|
253
|
+
});
|
|
254
|
+
it("normalises agent entries and merges organisations/users when requested", async () => {
|
|
255
|
+
const rest = makeRestStub();
|
|
256
|
+
rest.request = ((..._args) => Promise.resolve({
|
|
257
|
+
agents: [
|
|
258
|
+
{ id: "a1", name: "Agent One", type: "device" },
|
|
259
|
+
],
|
|
260
|
+
organisations: [{ id: "o1", name: "Org One" }],
|
|
261
|
+
users: [{ id: "u1", name: "User One" }],
|
|
262
|
+
}));
|
|
263
|
+
const api = new agents_api_1.AgentsApi(rest);
|
|
264
|
+
const result = await api.listAgents({
|
|
265
|
+
includeOrganisations: true,
|
|
266
|
+
includeUsers: true,
|
|
267
|
+
mergeIncludedAsAgents: true,
|
|
268
|
+
});
|
|
269
|
+
(0, chai_1.expect)(result.results).to.have.lengthOf(3);
|
|
270
|
+
(0, chai_1.expect)(result.count).to.equal(3);
|
|
271
|
+
const ids = (result.results ?? []).map((a) => a.id).sort();
|
|
272
|
+
(0, chai_1.expect)(ids).to.deep.equal(["a1", "o1", "u1"]);
|
|
273
|
+
const types = (result.results ?? []).map((a) => a.type).sort();
|
|
274
|
+
(0, chai_1.expect)(types).to.deep.equal(["device", "organisation", "user"]);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chai_1 = require("chai");
|
|
4
|
+
const gateway_client_1 = require("../gateway/gateway-client");
|
|
5
|
+
function makeConfig() {
|
|
6
|
+
return {
|
|
7
|
+
dataRestUrl: "https://example.com/api",
|
|
8
|
+
controlApiUrl: "https://example.com/control",
|
|
9
|
+
dataWssUrl: "wss://example.com/gateway",
|
|
10
|
+
disableBrowserLifecycleHooks: true,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function fakeMessage(channel = { agent_id: "a1", name: "c1" }, id = "m1") {
|
|
14
|
+
return {
|
|
15
|
+
id,
|
|
16
|
+
data: {},
|
|
17
|
+
attachments: [],
|
|
18
|
+
author_id: "auth",
|
|
19
|
+
channel,
|
|
20
|
+
timestamp: 1,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
describe("GatewayClient.subscribeToChannel", () => {
|
|
24
|
+
it("first handler triggers wire-level subscribe", () => {
|
|
25
|
+
const gw = new gateway_client_1.GatewayClient(makeConfig());
|
|
26
|
+
let subscribeCalls = 0;
|
|
27
|
+
gw.subscribe = () => {
|
|
28
|
+
subscribeCalls += 1;
|
|
29
|
+
};
|
|
30
|
+
gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: () => { } });
|
|
31
|
+
(0, chai_1.expect)(subscribeCalls).to.equal(1);
|
|
32
|
+
});
|
|
33
|
+
it("second handler does not trigger another wire-level subscribe", () => {
|
|
34
|
+
const gw = new gateway_client_1.GatewayClient(makeConfig());
|
|
35
|
+
let subscribeCalls = 0;
|
|
36
|
+
gw.subscribe = () => {
|
|
37
|
+
subscribeCalls += 1;
|
|
38
|
+
};
|
|
39
|
+
gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: () => { } });
|
|
40
|
+
gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: () => { } });
|
|
41
|
+
(0, chai_1.expect)(subscribeCalls).to.equal(1);
|
|
42
|
+
});
|
|
43
|
+
it("returned unsubscribe drops the handler; last drop unsubscribes", () => {
|
|
44
|
+
const gw = new gateway_client_1.GatewayClient(makeConfig());
|
|
45
|
+
let unsubCalls = 0;
|
|
46
|
+
gw.subscribe = () => { };
|
|
47
|
+
gw.unsubscribe = () => {
|
|
48
|
+
unsubCalls += 1;
|
|
49
|
+
};
|
|
50
|
+
const off1 = gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: () => { } });
|
|
51
|
+
const off2 = gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: () => { } });
|
|
52
|
+
off1();
|
|
53
|
+
(0, chai_1.expect)(unsubCalls).to.equal(0);
|
|
54
|
+
off2();
|
|
55
|
+
(0, chai_1.expect)(unsubCalls).to.equal(1);
|
|
56
|
+
});
|
|
57
|
+
it("returned unsubscribe is idempotent", () => {
|
|
58
|
+
const gw = new gateway_client_1.GatewayClient(makeConfig());
|
|
59
|
+
let unsubCalls = 0;
|
|
60
|
+
gw.subscribe = () => { };
|
|
61
|
+
gw.unsubscribe = () => {
|
|
62
|
+
unsubCalls += 1;
|
|
63
|
+
};
|
|
64
|
+
const off = gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: () => { } });
|
|
65
|
+
off();
|
|
66
|
+
off();
|
|
67
|
+
off();
|
|
68
|
+
(0, chai_1.expect)(unsubCalls).to.equal(1);
|
|
69
|
+
});
|
|
70
|
+
it("messageCreate event fans out to onMessage handlers for the matching channel", () => {
|
|
71
|
+
const gw = new gateway_client_1.GatewayClient(makeConfig());
|
|
72
|
+
gw.subscribe = () => { };
|
|
73
|
+
let received;
|
|
74
|
+
gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: (m) => { received = m; } });
|
|
75
|
+
gw.emit("messageCreate", fakeMessage());
|
|
76
|
+
(0, chai_1.expect)(received?.id).to.equal("m1");
|
|
77
|
+
});
|
|
78
|
+
it("messageCreate for a different channel does not fire the handler", () => {
|
|
79
|
+
const gw = new gateway_client_1.GatewayClient(makeConfig());
|
|
80
|
+
gw.subscribe = () => { };
|
|
81
|
+
let received = false;
|
|
82
|
+
gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onMessage: () => { received = true; } });
|
|
83
|
+
gw.emit("messageCreate", fakeMessage({ agent_id: "a1", name: "c2" }, "m2"));
|
|
84
|
+
(0, chai_1.expect)(received).to.equal(false);
|
|
85
|
+
});
|
|
86
|
+
it("aggregateUpdate fans out to onAggregate handlers", () => {
|
|
87
|
+
const gw = new gateway_client_1.GatewayClient(makeConfig());
|
|
88
|
+
gw.subscribe = () => { };
|
|
89
|
+
let received;
|
|
90
|
+
gw.subscribeToChannel({ agent_id: "a1", name: "c1" }, { onAggregate: (a) => { received = a; } });
|
|
91
|
+
const aggregate = { data: { x: 1 }, attachments: [] };
|
|
92
|
+
gw.emit("aggregateUpdate", { channel: { agent_id: "a1", name: "c1" }, aggregate });
|
|
93
|
+
(0, chai_1.expect)(received?.data).to.deep.equal({ x: 1 });
|
|
94
|
+
});
|
|
95
|
+
});
|
package/dist/test/react.test.js
CHANGED
|
@@ -336,6 +336,42 @@ function wrapper(client) {
|
|
|
336
336
|
});
|
|
337
337
|
await (0, react_1.waitFor)(() => (0, chai_1.expect)(result.current.data?.response?.status).to.equal("success"));
|
|
338
338
|
});
|
|
339
|
+
(0, mocha_1.it)("useInvocationLogs fetches logs for the message", async () => {
|
|
340
|
+
const messageId = (0, snowflake_1.generateSnowflakeIdAtTime)(new Date("2026-01-01T00:00:00.000Z"));
|
|
341
|
+
const fetchMock = (0, helpers_1.createFetchMock)(() => (0, helpers_1.createJsonResponse)([
|
|
342
|
+
{ level: "INFO", message: "hello", timestamp: 1 },
|
|
343
|
+
{ level: "ERROR", message: "boom", timestamp: 2 },
|
|
344
|
+
]));
|
|
345
|
+
const client = new doover_client_1.DooverClient({
|
|
346
|
+
dataRestUrl: "https://api.example.com",
|
|
347
|
+
controlApiUrl: "https://control.example.com",
|
|
348
|
+
dataWssUrl: "wss://ws.example.com",
|
|
349
|
+
fetchImpl: fetchMock,
|
|
350
|
+
webSocketImpl: helpers_1.MockWebSocket,
|
|
351
|
+
disableBrowserLifecycleHooks: true,
|
|
352
|
+
});
|
|
353
|
+
const { result } = (0, react_1.renderHook)(() => (0, react_2.useInvocationLogs)({ agentId: "a1", channelName: "dv-proc-inv-1" }, messageId), { wrapper: wrapper(client) });
|
|
354
|
+
await (0, react_1.waitFor)(() => (0, chai_1.expect)(result.current.isSuccess).to.equal(true));
|
|
355
|
+
(0, chai_1.expect)(result.current.data?.map((l) => l.level)).to.deep.equal([
|
|
356
|
+
"INFO",
|
|
357
|
+
"ERROR",
|
|
358
|
+
]);
|
|
359
|
+
const requestUrl = fetchMock.firstCall?.args[0];
|
|
360
|
+
(0, chai_1.expect)(requestUrl).to.contain(`/agents/a1/channels/dv-proc-inv-1/messages/${messageId}/logs`);
|
|
361
|
+
});
|
|
362
|
+
(0, mocha_1.it)("useInvocationLogs is disabled when messageId is undefined", () => {
|
|
363
|
+
const fetchMock = (0, helpers_1.createFetchMock)(() => (0, helpers_1.createJsonResponse)([]));
|
|
364
|
+
const client = new doover_client_1.DooverClient({
|
|
365
|
+
dataRestUrl: "https://api.example.com",
|
|
366
|
+
controlApiUrl: "https://control.example.com",
|
|
367
|
+
dataWssUrl: "wss://ws.example.com",
|
|
368
|
+
fetchImpl: fetchMock,
|
|
369
|
+
webSocketImpl: helpers_1.MockWebSocket,
|
|
370
|
+
disableBrowserLifecycleHooks: true,
|
|
371
|
+
});
|
|
372
|
+
(0, react_1.renderHook)(() => (0, react_2.useInvocationLogs)({ agentId: "a1", channelName: "dv-proc-inv-1" }, undefined), { wrapper: wrapper(client) });
|
|
373
|
+
(0, chai_1.expect)(fetchMock.callCount).to.equal(0);
|
|
374
|
+
});
|
|
339
375
|
(0, mocha_1.it)("getSharedQueryClient returns the same QueryClient across callers", () => {
|
|
340
376
|
(0, react_2.resetSharedQueryClient)();
|
|
341
377
|
const a = (0, react_2.getSharedQueryClient)();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chai_1 = require("chai");
|
|
4
|
+
const node_events_1 = require("node:events");
|
|
5
|
+
const rpc_dispatcher_1 = require("../rpc/rpc-dispatcher");
|
|
6
|
+
const errors_1 = require("../rpc/errors");
|
|
7
|
+
const stats_1 = require("../client/stats");
|
|
8
|
+
function makeFakeGateway() {
|
|
9
|
+
const ee = new node_events_1.EventEmitter();
|
|
10
|
+
let subscribeCalls = 0;
|
|
11
|
+
let unsubscribeCalls = 0;
|
|
12
|
+
const channels = new Map();
|
|
13
|
+
const key = (c) => `${c.agent_id}/${c.name}`;
|
|
14
|
+
const subscribeToChannel = (channel, handlers) => {
|
|
15
|
+
const k = key(channel);
|
|
16
|
+
let set = channels.get(k);
|
|
17
|
+
if (!set) {
|
|
18
|
+
set = new Set();
|
|
19
|
+
channels.set(k, set);
|
|
20
|
+
subscribeCalls += 1;
|
|
21
|
+
}
|
|
22
|
+
set.add(handlers);
|
|
23
|
+
ee.on("messageUpdate", (msg, requestData) => {
|
|
24
|
+
if (msg.channel.agent_id !== channel.agent_id || msg.channel.name !== channel.name)
|
|
25
|
+
return;
|
|
26
|
+
handlers.onMessageUpdate?.(msg, requestData);
|
|
27
|
+
});
|
|
28
|
+
let off = false;
|
|
29
|
+
return () => {
|
|
30
|
+
if (off)
|
|
31
|
+
return;
|
|
32
|
+
off = true;
|
|
33
|
+
set.delete(handlers);
|
|
34
|
+
if (set.size === 0) {
|
|
35
|
+
channels.delete(k);
|
|
36
|
+
unsubscribeCalls += 1;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
subscribeToChannel: subscribeToChannel,
|
|
42
|
+
emitMessageUpdate: (msg, rd) => ee.emit("messageUpdate", msg, rd),
|
|
43
|
+
get subscribeCalls() { return subscribeCalls; },
|
|
44
|
+
get unsubscribeCalls() { return unsubscribeCalls; },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function makeFakeMessagesApi() {
|
|
48
|
+
let nextId = "msg-1";
|
|
49
|
+
const posts = [];
|
|
50
|
+
const postMessage = (async (a, b, c) => {
|
|
51
|
+
const isPositional = typeof a === "string";
|
|
52
|
+
const agentId = isPositional ? a : a.agentId;
|
|
53
|
+
const channelName = isPositional ? b : a.channelName;
|
|
54
|
+
const body = isPositional ? c : b;
|
|
55
|
+
posts.push({ agentId, channelName, body });
|
|
56
|
+
const message = {
|
|
57
|
+
id: nextId,
|
|
58
|
+
data: body,
|
|
59
|
+
attachments: [],
|
|
60
|
+
author_id: "auth",
|
|
61
|
+
channel: { agent_id: agentId, name: channelName },
|
|
62
|
+
timestamp: 1,
|
|
63
|
+
};
|
|
64
|
+
return message;
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
postMessage,
|
|
68
|
+
posts,
|
|
69
|
+
setNextId: (id) => { nextId = id; },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function rpcMessage(id, channel, status, request, response) {
|
|
73
|
+
return {
|
|
74
|
+
id,
|
|
75
|
+
data: { method: "do", request, status, response: response },
|
|
76
|
+
attachments: [],
|
|
77
|
+
author_id: "auth",
|
|
78
|
+
channel,
|
|
79
|
+
timestamp: 1,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
describe("RpcDispatcher", () => {
|
|
83
|
+
it("send resolves on terminal success status", async () => {
|
|
84
|
+
const gw = makeFakeGateway();
|
|
85
|
+
const messages = makeFakeMessagesApi();
|
|
86
|
+
messages.setNextId("rpc-1");
|
|
87
|
+
const dispatcher = new rpc_dispatcher_1.RpcDispatcher(gw, messages);
|
|
88
|
+
const channel = { agent_id: "a1", name: "c1" };
|
|
89
|
+
const promise = dispatcher.send({ agentId: "a1", channelName: "c1" }, { method: "do", request: { x: 1 } });
|
|
90
|
+
// wait one microtask so postMessage settles and pending is registered
|
|
91
|
+
await new Promise((r) => setImmediate(r));
|
|
92
|
+
gw.emitMessageUpdate(rpcMessage("rpc-1", channel, { code: "success" }, { x: 1 }, { ok: true }));
|
|
93
|
+
const result = await promise;
|
|
94
|
+
(0, chai_1.expect)(result).to.deep.equal({ ok: true });
|
|
95
|
+
(0, chai_1.expect)(gw.subscribeCalls).to.equal(1);
|
|
96
|
+
(0, chai_1.expect)(gw.unsubscribeCalls).to.equal(1);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe("RpcDispatcher additional outcomes", () => {
|
|
100
|
+
it("rejects with DooverRpcError on terminal error status", async () => {
|
|
101
|
+
const gw = makeFakeGateway();
|
|
102
|
+
const messages = makeFakeMessagesApi();
|
|
103
|
+
messages.setNextId("rpc-2");
|
|
104
|
+
const dispatcher = new rpc_dispatcher_1.RpcDispatcher(gw, messages);
|
|
105
|
+
const channel = { agent_id: "a1", name: "c1" };
|
|
106
|
+
const promise = dispatcher.send({ agentId: "a1", channelName: "c1" }, { method: "do", request: {} });
|
|
107
|
+
await new Promise((r) => setImmediate(r));
|
|
108
|
+
gw.emitMessageUpdate(rpcMessage("rpc-2", channel, { code: "error", message: "nope" }, {}));
|
|
109
|
+
let caught;
|
|
110
|
+
try {
|
|
111
|
+
await promise;
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
caught = e;
|
|
115
|
+
}
|
|
116
|
+
(0, chai_1.expect)(caught).to.be.instanceOf(errors_1.DooverRpcError);
|
|
117
|
+
(0, chai_1.expect)(caught.message).to.equal("nope");
|
|
118
|
+
});
|
|
119
|
+
it("rejects on timeoutMs with no terminal status", async () => {
|
|
120
|
+
const gw = makeFakeGateway();
|
|
121
|
+
const messages = makeFakeMessagesApi();
|
|
122
|
+
const dispatcher = new rpc_dispatcher_1.RpcDispatcher(gw, messages);
|
|
123
|
+
const promise = dispatcher.send({ agentId: "a1", channelName: "c1" }, { method: "do", request: {} }, { timeoutMs: 5 });
|
|
124
|
+
let caught;
|
|
125
|
+
try {
|
|
126
|
+
await promise;
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
caught = e;
|
|
130
|
+
}
|
|
131
|
+
(0, chai_1.expect)(caught?.message).to.equal("RPC timed out");
|
|
132
|
+
});
|
|
133
|
+
it("rejects on AbortSignal.abort()", async () => {
|
|
134
|
+
const gw = makeFakeGateway();
|
|
135
|
+
const messages = makeFakeMessagesApi();
|
|
136
|
+
const dispatcher = new rpc_dispatcher_1.RpcDispatcher(gw, messages);
|
|
137
|
+
const ac = new AbortController();
|
|
138
|
+
const promise = dispatcher.send({ agentId: "a1", channelName: "c1" }, { method: "do", request: {} }, { signal: ac.signal });
|
|
139
|
+
await new Promise((r) => setImmediate(r));
|
|
140
|
+
ac.abort(new Error("user cancelled"));
|
|
141
|
+
let caught;
|
|
142
|
+
try {
|
|
143
|
+
await promise;
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
caught = e;
|
|
147
|
+
}
|
|
148
|
+
(0, chai_1.expect)(caught?.message).to.equal("user cancelled");
|
|
149
|
+
});
|
|
150
|
+
it("two concurrent RPCs on the same channel share one gateway subscription", async () => {
|
|
151
|
+
const gw = makeFakeGateway();
|
|
152
|
+
const messages = makeFakeMessagesApi();
|
|
153
|
+
const dispatcher = new rpc_dispatcher_1.RpcDispatcher(gw, messages);
|
|
154
|
+
messages.setNextId("rpc-A");
|
|
155
|
+
const pA = dispatcher.send({ agentId: "a1", channelName: "c1" }, { method: "do", request: { n: 1 } });
|
|
156
|
+
await new Promise((r) => setImmediate(r));
|
|
157
|
+
messages.setNextId("rpc-B");
|
|
158
|
+
const pB = dispatcher.send({ agentId: "a1", channelName: "c1" }, { method: "do", request: { n: 2 } });
|
|
159
|
+
await new Promise((r) => setImmediate(r));
|
|
160
|
+
(0, chai_1.expect)(gw.subscribeCalls).to.equal(1);
|
|
161
|
+
const channel = { agent_id: "a1", name: "c1" };
|
|
162
|
+
gw.emitMessageUpdate(rpcMessage("rpc-A", channel, { code: "success" }, { n: 1 }, { a: true }));
|
|
163
|
+
gw.emitMessageUpdate(rpcMessage("rpc-B", channel, { code: "success" }, { n: 2 }, { b: true }));
|
|
164
|
+
(0, chai_1.expect)(await pA).to.deep.equal({ a: true });
|
|
165
|
+
(0, chai_1.expect)(await pB).to.deep.equal({ b: true });
|
|
166
|
+
(0, chai_1.expect)(gw.unsubscribeCalls).to.equal(1);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
describe("RpcDispatcher stats integration", () => {
|
|
170
|
+
it("records start and end via collector when enabled", async () => {
|
|
171
|
+
const gw = makeFakeGateway();
|
|
172
|
+
const messages = makeFakeMessagesApi();
|
|
173
|
+
const dispatcher = new rpc_dispatcher_1.RpcDispatcher(gw, messages);
|
|
174
|
+
const stats = new stats_1.DooverStatsCollector();
|
|
175
|
+
stats.setEnabled(true);
|
|
176
|
+
dispatcher.setStats(stats);
|
|
177
|
+
messages.setNextId("rpc-stats");
|
|
178
|
+
const channel = { agent_id: "a1", name: "c1" };
|
|
179
|
+
const p = dispatcher.send({ agentId: "a1", channelName: "c1" }, { method: "do", request: {} });
|
|
180
|
+
await new Promise((r) => setImmediate(r));
|
|
181
|
+
(0, chai_1.expect)(stats.snapshot().rpc.pendingRpcs).to.equal(1);
|
|
182
|
+
gw.emitMessageUpdate(rpcMessage("rpc-stats", channel, { code: "success" }, {}, { ok: true }));
|
|
183
|
+
await p;
|
|
184
|
+
const snap = stats.snapshot();
|
|
185
|
+
(0, chai_1.expect)(snap.rpc.totalRpcs).to.equal(1);
|
|
186
|
+
(0, chai_1.expect)(snap.rpc.completedRpcs).to.equal(1);
|
|
187
|
+
(0, chai_1.expect)(snap.rpc.pendingRpcs).to.equal(0);
|
|
188
|
+
(0, chai_1.expect)(snap.rpc.peakPendingRpcs).to.equal(1);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|