doover-js 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apis/alarms-api.d.ts +6 -0
- package/dist/apis/alarms-api.js +7 -0
- package/dist/apis/notifications-api.d.ts +20 -1
- package/dist/apis/notifications-api.js +17 -0
- package/dist/client/multiplex-client.js +3 -0
- package/dist/gateway/gateway-client.js +38 -0
- package/dist/test/apis.test.js +29 -2
- package/dist/test/gateway-client.test.js +78 -0
- package/dist/types/common.d.ts +47 -0
- package/dist/types/openapi.d.ts +30 -4
- package/package.json +1 -1
|
@@ -3,6 +3,12 @@ import type { Alarm, CreateAlarmRequest, PatchAlarmRequest } from "../types/open
|
|
|
3
3
|
export declare class AlarmsApi {
|
|
4
4
|
private readonly rest;
|
|
5
5
|
constructor(rest: RestClient);
|
|
6
|
+
/** Every alarm on every channel of an agent the caller can see. */
|
|
7
|
+
listAgentAlarms(agentId: string): Promise<Alarm[]>;
|
|
8
|
+
listAgentAlarms(identifier: {
|
|
9
|
+
agentId: string;
|
|
10
|
+
}): Promise<Alarm[]>;
|
|
11
|
+
private _listAgentAlarms;
|
|
6
12
|
listAlarms(agentId: string, channelName: string): Promise<Alarm[]>;
|
|
7
13
|
listAlarms(identifier: {
|
|
8
14
|
agentId: string;
|
package/dist/apis/alarms-api.js
CHANGED
|
@@ -6,6 +6,13 @@ class AlarmsApi {
|
|
|
6
6
|
constructor(rest) {
|
|
7
7
|
this.rest = rest;
|
|
8
8
|
}
|
|
9
|
+
listAgentAlarms(...args) {
|
|
10
|
+
const { agentId } = (0, _args_1.resolveAgentArgs)(args);
|
|
11
|
+
return this._listAgentAlarms(agentId);
|
|
12
|
+
}
|
|
13
|
+
_listAgentAlarms(agentId) {
|
|
14
|
+
return this.rest.get(`/agents/${agentId}/alarms`);
|
|
15
|
+
}
|
|
9
16
|
listAlarms(...args) {
|
|
10
17
|
const { agentId, channelName } = (0, _args_1.resolveChannelArgs)(args);
|
|
11
18
|
return this._listAlarms(agentId, channelName);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RestClient } from "../http/rest-client";
|
|
2
|
-
import type { CreateEndpointRequest, CreateNotificationSubscriptionRequest, NotificationDataResponse, NotificationEndpoint, NotificationEndpointsResponse, NotificationSubscribersResponse, NotificationSubscriptionCreateResponse, NotificationSubscriptionsResponse, UpdateEndpointRequest, UpdateMeWebPushEndpointRequest, UpdateNotificationSubscriptionRequest } from "../types/openapi";
|
|
2
|
+
import type { CreateEndpointRequest, CreateNotificationSubscriptionRequest, NotificationDataResponse, NotificationEndpoint, NotificationEndpointSummariesResponse, NotificationEndpointsResponse, NotificationSubscribersResponse, NotificationSubscriptionCreateResponse, NotificationSubscriptionsResponse, UpdateEndpointRequest, UpdateMeWebPushEndpointRequest, UpdateNotificationSubscriptionRequest } from "../types/openapi";
|
|
3
3
|
export declare class NotificationsApi {
|
|
4
4
|
private readonly rest;
|
|
5
5
|
constructor(rest: RestClient);
|
|
@@ -13,6 +13,16 @@ export declare class NotificationsApi {
|
|
|
13
13
|
agentId: string;
|
|
14
14
|
}, name?: string): Promise<NotificationEndpointsResponse>;
|
|
15
15
|
private _getAgentNotificationEndpoints;
|
|
16
|
+
/**
|
|
17
|
+
* Delivery readiness for each endpoint, without addresses or credentials.
|
|
18
|
+
* Readable with either notification-read or manage-subscriptions rights, so
|
|
19
|
+
* a delegated subscription manager can list endpoints it can't otherwise see.
|
|
20
|
+
*/
|
|
21
|
+
getAgentNotificationEndpointSummaries(agentId: string): Promise<NotificationEndpointSummariesResponse>;
|
|
22
|
+
getAgentNotificationEndpointSummaries(identifier: {
|
|
23
|
+
agentId: string;
|
|
24
|
+
}): Promise<NotificationEndpointSummariesResponse>;
|
|
25
|
+
private _getAgentNotificationEndpointSummaries;
|
|
16
26
|
createNotificationEndpoint(agentId: string, body: CreateEndpointRequest): Promise<NotificationEndpoint>;
|
|
17
27
|
createNotificationEndpoint(identifier: {
|
|
18
28
|
agentId: string;
|
|
@@ -48,6 +58,15 @@ export declare class NotificationsApi {
|
|
|
48
58
|
agentId: string;
|
|
49
59
|
}, subscribed_to?: string): Promise<NotificationSubscriptionsResponse>;
|
|
50
60
|
private _getAgentDefaultNotificationSubscriptions;
|
|
61
|
+
/**
|
|
62
|
+
* Update every default subscription this subscriber holds on `subscribedTo`
|
|
63
|
+
* at once. Omitted fields keep their current value.
|
|
64
|
+
*/
|
|
65
|
+
updateDefaultNotificationSubscription(agentId: string, subscribedTo: string, body: UpdateNotificationSubscriptionRequest): Promise<unknown>;
|
|
66
|
+
updateDefaultNotificationSubscription(identifier: {
|
|
67
|
+
agentId: string;
|
|
68
|
+
}, subscribedTo: string, body: UpdateNotificationSubscriptionRequest): Promise<unknown>;
|
|
69
|
+
private _updateDefaultNotificationSubscription;
|
|
51
70
|
deleteDefaultNotificationSubscription(agentId: string, subscribedTo: string): Promise<unknown>;
|
|
52
71
|
deleteDefaultNotificationSubscription(identifier: {
|
|
53
72
|
agentId: string;
|
|
@@ -23,6 +23,13 @@ class NotificationsApi {
|
|
|
23
23
|
_getAgentNotificationEndpoints(agentId, name) {
|
|
24
24
|
return this.rest.get(`/agents/${agentId}/notifications/endpoints`, { name });
|
|
25
25
|
}
|
|
26
|
+
getAgentNotificationEndpointSummaries(...args) {
|
|
27
|
+
const { agentId } = (0, _args_1.resolveAgentArgs)(args);
|
|
28
|
+
return this._getAgentNotificationEndpointSummaries(agentId);
|
|
29
|
+
}
|
|
30
|
+
_getAgentNotificationEndpointSummaries(agentId) {
|
|
31
|
+
return this.rest.get(`/agents/${agentId}/notifications/endpoints/summary`);
|
|
32
|
+
}
|
|
26
33
|
createNotificationEndpoint(...args) {
|
|
27
34
|
if (typeof args[0] === "string") {
|
|
28
35
|
return this._createNotificationEndpoint(args[0], args[1]);
|
|
@@ -93,6 +100,16 @@ class NotificationsApi {
|
|
|
93
100
|
_getAgentDefaultNotificationSubscriptions(agentId, subscribed_to) {
|
|
94
101
|
return this.rest.get(`/agents/${agentId}/notifications/subscriptions/default`, { subscribed_to });
|
|
95
102
|
}
|
|
103
|
+
updateDefaultNotificationSubscription(...args) {
|
|
104
|
+
if (typeof args[0] === "string") {
|
|
105
|
+
return this._updateDefaultNotificationSubscription(args[0], args[1], args[2]);
|
|
106
|
+
}
|
|
107
|
+
const id = args[0];
|
|
108
|
+
return this._updateDefaultNotificationSubscription(id.agentId, args[1], args[2]);
|
|
109
|
+
}
|
|
110
|
+
_updateDefaultNotificationSubscription(agentId, subscribedTo, body) {
|
|
111
|
+
return this.rest.patch(`/agents/${agentId}/notifications/subscriptions/default/${subscribedTo}`, body);
|
|
112
|
+
}
|
|
96
113
|
deleteDefaultNotificationSubscription(...args) {
|
|
97
114
|
if (typeof args[0] === "string") {
|
|
98
115
|
return this._deleteDefaultNotificationSubscription(args[0], args[1]);
|
|
@@ -9,6 +9,7 @@ const multiplex_gateway_1 = require("./multiplex-gateway");
|
|
|
9
9
|
/** "<subclient>.<method>" → spec. Drives the generic non-core facades. */
|
|
10
10
|
const NONCORE_METHODS = {
|
|
11
11
|
// alarms
|
|
12
|
+
"alarms.listAgentAlarms": { cap: "alarms.read", kind: "read-fanout-first", agentScoped: true },
|
|
12
13
|
"alarms.listAlarms": { cap: "alarms.read", kind: "read-fanout-first", agentScoped: true },
|
|
13
14
|
"alarms.getAlarm": { cap: "alarms.read", kind: "read-fanout-first", agentScoped: true },
|
|
14
15
|
"alarms.createAlarm": { cap: "alarms.write", kind: "write-route", agentScoped: true },
|
|
@@ -25,6 +26,7 @@ const NONCORE_METHODS = {
|
|
|
25
26
|
// notifications (reads)
|
|
26
27
|
"notifications.getAgentNotifications": { cap: "notifications.read", kind: "read-fanout-first", agentScoped: true },
|
|
27
28
|
"notifications.getAgentNotificationEndpoints": { cap: "notifications.read", kind: "read-fanout-first", agentScoped: true },
|
|
29
|
+
"notifications.getAgentNotificationEndpointSummaries": { cap: "notifications.read", kind: "read-fanout-first", agentScoped: true },
|
|
28
30
|
"notifications.getAgentNotificationSubscriptions": { cap: "notifications.read", kind: "read-fanout-first", agentScoped: true },
|
|
29
31
|
"notifications.getAgentDefaultNotificationSubscriptions": { cap: "notifications.read", kind: "read-fanout-first", agentScoped: true },
|
|
30
32
|
"notifications.getAgentNotificationSubscribers": { cap: "notifications.read", kind: "read-fanout-first", agentScoped: true },
|
|
@@ -35,6 +37,7 @@ const NONCORE_METHODS = {
|
|
|
35
37
|
"notifications.deleteNotificationEndpoint": { cap: "notifications.write", kind: "write-route", agentScoped: true },
|
|
36
38
|
"notifications.testNotificationEndpoint": { cap: "notifications.write", kind: "write-route", agentScoped: true },
|
|
37
39
|
"notifications.createNotificationSubscription": { cap: "notifications.write", kind: "write-route", agentScoped: true },
|
|
40
|
+
"notifications.updateDefaultNotificationSubscription": { cap: "notifications.write", kind: "write-route", agentScoped: true },
|
|
38
41
|
"notifications.deleteDefaultNotificationSubscription": { cap: "notifications.write", kind: "write-route", agentScoped: true },
|
|
39
42
|
"notifications.updateNotificationSubscription": { cap: "notifications.write", kind: "write-route", agentScoped: true },
|
|
40
43
|
"notifications.deleteNotificationSubscription": { cap: "notifications.write", kind: "write-route", agentScoped: true },
|
|
@@ -5,6 +5,40 @@ const errors_1 = require("../http/errors");
|
|
|
5
5
|
const snowflake_1 = require("../utils/snowflake");
|
|
6
6
|
const RECONNECT_BASE_MS = 1000;
|
|
7
7
|
const RECONNECT_CAP_MS = 30000;
|
|
8
|
+
/** Schemes a WebSocket can be opened on — it maps http(s) to ws(s) itself. */
|
|
9
|
+
const ALLOWED_WSS_PROTOCOLS = new Set(["ws:", "wss:", "http:", "https:"]);
|
|
10
|
+
/**
|
|
11
|
+
* Reject a `dataWssUrl` that cannot address a gateway.
|
|
12
|
+
*
|
|
13
|
+
* An empty or relative value is the dangerous case, because nothing rejects it
|
|
14
|
+
* on its own: the WebSocket constructor resolves a relative URL against the
|
|
15
|
+
* document and maps https to wss, so in a browser `new WebSocket("")` silently
|
|
16
|
+
* points the gateway at whatever page is open. That page answers the upgrade
|
|
17
|
+
* with its own HTML and a 200, the browser reports "the server did not accept
|
|
18
|
+
* the WebSocket handshake", and the client retries against it forever — a loop
|
|
19
|
+
* that looks like a server problem and never names the real cause. It reached
|
|
20
|
+
* a kiosk panel that way, from a client built with `dataWssUrl: ""` because the
|
|
21
|
+
* build it was bundled into defined no env for it.
|
|
22
|
+
*
|
|
23
|
+
* Absolute http(s) is allowed as well as ws(s), since the WebSocket
|
|
24
|
+
* constructor maps those itself and existing configs rely on it.
|
|
25
|
+
*/
|
|
26
|
+
function assertAbsoluteWssUrl(url) {
|
|
27
|
+
if (typeof url !== "string" || url.trim() === "") {
|
|
28
|
+
throw new Error("dataWssUrl is empty, so a gateway connection would resolve against the " +
|
|
29
|
+
"current page rather than a gateway; set it to an absolute ws(s):// URL");
|
|
30
|
+
}
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
parsed = new URL(url);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new Error(`dataWssUrl must be an absolute ws(s):// URL, not the relative value ${JSON.stringify(url)}`);
|
|
37
|
+
}
|
|
38
|
+
if (!ALLOWED_WSS_PROTOCOLS.has(parsed.protocol)) {
|
|
39
|
+
throw new Error(`dataWssUrl must be ws(s):// or http(s)://, not ${parsed.protocol}//`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
8
42
|
class GatewayClient {
|
|
9
43
|
constructor(config, auth) {
|
|
10
44
|
this.config = config;
|
|
@@ -90,6 +124,10 @@ class GatewayClient {
|
|
|
90
124
|
}
|
|
91
125
|
}
|
|
92
126
|
async openSocket() {
|
|
127
|
+
// Before auth, before any socket: a URL that cannot address a gateway is a
|
|
128
|
+
// configuration fault, and finding out now costs one error instead of an
|
|
129
|
+
// endless handshake failure that reads like a broken server.
|
|
130
|
+
assertAbsoluteWssUrl(this.config.dataWssUrl);
|
|
93
131
|
if (this.auth) {
|
|
94
132
|
await this.auth.ensureReady();
|
|
95
133
|
}
|
package/dist/test/apis.test.js
CHANGED
|
@@ -290,10 +290,23 @@ function setupRest(responseFactory) {
|
|
|
290
290
|
await alarms.createAlarm("a1", "c1", { name: "a", key: "x", operator: "gt", value: 1 });
|
|
291
291
|
await alarms.getAlarm("a1", "c1", "alarm");
|
|
292
292
|
await alarms.putAlarm("a1", "c1", "alarm", { name: "a", key: "x", operator: "gt", value: 1 });
|
|
293
|
+
// A rate alarm states its condition with rate_threshold/rate_window_ms and
|
|
294
|
+
// must leave `value` unset.
|
|
295
|
+
await alarms.createAlarm("a1", "c1", {
|
|
296
|
+
name: "rising",
|
|
297
|
+
key: "level",
|
|
298
|
+
operator: "gt",
|
|
299
|
+
rate_threshold: 0.05,
|
|
300
|
+
rate_window_ms: 600000,
|
|
301
|
+
topic_name: "level-rising",
|
|
302
|
+
notification_policy: "opt-in",
|
|
303
|
+
messages: { alarm: { notify: true, text: "Level rising fast" } },
|
|
304
|
+
});
|
|
293
305
|
await alarms.patchAlarm("a1", "c1", "alarm", { enabled: false });
|
|
306
|
+
await alarms.patchAlarm("a1", "c1", "alarm", { messages: { ok: { notify: false } } });
|
|
294
307
|
await alarms.deleteAlarm("a1", "c1", "alarm");
|
|
295
308
|
(0, chai_1.expect)(await aggregateBlob.text()).to.equal("agg");
|
|
296
|
-
(0, chai_1.expect)(fetchMock.callCount).to.equal(
|
|
309
|
+
(0, chai_1.expect)(fetchMock.callCount).to.equal(12);
|
|
297
310
|
});
|
|
298
311
|
(0, mocha_1.it)("covers connections and notifications methods", async () => {
|
|
299
312
|
const { rest, fetchMock } = setupRest(() => (0, helpers_1.createJsonResponse)({ subscriptions: [], endpoints: [], subscribers: [] }));
|
|
@@ -312,6 +325,7 @@ function setupRest(responseFactory) {
|
|
|
312
325
|
(0, chai_1.expect)(syncCall.args[1].method).to.equal("POST");
|
|
313
326
|
await notifications.getAgentNotifications("a1");
|
|
314
327
|
await notifications.getAgentNotificationEndpoints("a1", "browser");
|
|
328
|
+
await notifications.getAgentNotificationEndpointSummaries("a1");
|
|
315
329
|
await notifications.createNotificationEndpoint("a1", {
|
|
316
330
|
name: "browser",
|
|
317
331
|
type: 3,
|
|
@@ -328,6 +342,11 @@ function setupRest(responseFactory) {
|
|
|
328
342
|
topic_filter: ["*"],
|
|
329
343
|
});
|
|
330
344
|
await notifications.getAgentDefaultNotificationSubscriptions("a1", "a2");
|
|
345
|
+
await notifications.updateDefaultNotificationSubscription("a1", "a2", {
|
|
346
|
+
severity: 5,
|
|
347
|
+
topic_filter: ["^dev/alarms/default/[^/]+/triggered$"],
|
|
348
|
+
topic_filter_mode: "regex",
|
|
349
|
+
});
|
|
331
350
|
await notifications.deleteDefaultNotificationSubscription("a1", "a2");
|
|
332
351
|
await notifications.updateNotificationSubscription("a1", "s1", { severity: 4 });
|
|
333
352
|
await notifications.deleteNotificationSubscription("a1", "s1");
|
|
@@ -340,7 +359,15 @@ function setupRest(responseFactory) {
|
|
|
340
359
|
expires_at: 1,
|
|
341
360
|
});
|
|
342
361
|
await notifications.getWebPushPublicKey();
|
|
343
|
-
|
|
362
|
+
const summaryCall = fetchMock
|
|
363
|
+
.getCalls()
|
|
364
|
+
.find((call) => call.args[0].endsWith("/agents/a1/notifications/endpoints/summary"));
|
|
365
|
+
(0, chai_1.expect)(summaryCall).to.not.equal(undefined);
|
|
366
|
+
const defaultPatch = fetchMock
|
|
367
|
+
.getCalls()
|
|
368
|
+
.find((call) => call.args[0].endsWith("/agents/a1/notifications/subscriptions/default/a2") && call.args[1].method === "PATCH");
|
|
369
|
+
(0, chai_1.expect)(defaultPatch).to.not.equal(undefined);
|
|
370
|
+
(0, chai_1.expect)(fetchMock.callCount).to.equal(23);
|
|
344
371
|
});
|
|
345
372
|
(0, mocha_1.it)("covers permissions, processors, and turn methods", async () => {
|
|
346
373
|
const { rest, fetchMock } = setupRest(() => (0, helpers_1.createJsonResponse)({ db: { agent_id: "a1", is_superuser: false, resources: [] } }));
|
|
@@ -393,4 +393,82 @@ class RejectingAuth extends doover_auth_1.DooverAuth {
|
|
|
393
393
|
(0, chai_1.expect)(helpers_1.MockWebSocket.instances.length).to.equal(11);
|
|
394
394
|
randomStub.restore();
|
|
395
395
|
});
|
|
396
|
+
(0, mocha_1.describe)("an unusable dataWssUrl", () => {
|
|
397
|
+
// A relative or empty value is resolved against the document by the
|
|
398
|
+
// WebSocket constructor, so the gateway silently points at whatever page
|
|
399
|
+
// is open, gets that page's HTML and a 200 back, and retries against it
|
|
400
|
+
// forever. Refusing it names the cause once instead.
|
|
401
|
+
(0, mocha_1.it)("refuses an empty one rather than resolving against the page", async () => {
|
|
402
|
+
const client = new gateway_client_1.GatewayClient({
|
|
403
|
+
dataRestUrl: "https://api.example.com",
|
|
404
|
+
controlApiUrl: "https://control.example.com",
|
|
405
|
+
dataWssUrl: "",
|
|
406
|
+
organisationId: null,
|
|
407
|
+
sharing: "none",
|
|
408
|
+
impersonateUserStorageKey: "doover:test",
|
|
409
|
+
});
|
|
410
|
+
await (0, chai_1.expect)(client.connect()).to.be.rejectedWith(/dataWssUrl is empty/);
|
|
411
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances).to.have.length(0);
|
|
412
|
+
});
|
|
413
|
+
(0, mocha_1.it)("refuses a relative one", async () => {
|
|
414
|
+
const client = new gateway_client_1.GatewayClient({
|
|
415
|
+
dataRestUrl: "https://api.example.com",
|
|
416
|
+
controlApiUrl: "https://control.example.com",
|
|
417
|
+
dataWssUrl: "/widget/some_widget?app_key=k",
|
|
418
|
+
organisationId: null,
|
|
419
|
+
sharing: "none",
|
|
420
|
+
impersonateUserStorageKey: "doover:test",
|
|
421
|
+
});
|
|
422
|
+
await (0, chai_1.expect)(client.connect()).to.be.rejectedWith(/not the relative value/);
|
|
423
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances).to.have.length(0);
|
|
424
|
+
});
|
|
425
|
+
(0, mocha_1.it)("refuses a scheme a WebSocket cannot be opened on", async () => {
|
|
426
|
+
const client = new gateway_client_1.GatewayClient({
|
|
427
|
+
dataRestUrl: "https://api.example.com",
|
|
428
|
+
controlApiUrl: "https://control.example.com",
|
|
429
|
+
dataWssUrl: "file:///etc/hosts",
|
|
430
|
+
organisationId: null,
|
|
431
|
+
sharing: "none",
|
|
432
|
+
impersonateUserStorageKey: "doover:test",
|
|
433
|
+
});
|
|
434
|
+
await (0, chai_1.expect)(client.connect()).to.be.rejectedWith(/must be ws\(s\):\/\/ or http\(s\)/);
|
|
435
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances).to.have.length(0);
|
|
436
|
+
});
|
|
437
|
+
(0, mocha_1.it)("reports it through wssError once when a subscribe connects in the background", async () => {
|
|
438
|
+
// `subscribe` is fire-and-forget, so the fault has to surface as an event
|
|
439
|
+
// rather than a rejected promise nobody holds — and only once, because
|
|
440
|
+
// reconnects are scheduled on socket close and no socket was ever opened.
|
|
441
|
+
const client = new gateway_client_1.GatewayClient({
|
|
442
|
+
dataRestUrl: "https://api.example.com",
|
|
443
|
+
controlApiUrl: "https://control.example.com",
|
|
444
|
+
dataWssUrl: "",
|
|
445
|
+
organisationId: null,
|
|
446
|
+
sharing: "none",
|
|
447
|
+
impersonateUserStorageKey: "doover:test",
|
|
448
|
+
});
|
|
449
|
+
const errors = [];
|
|
450
|
+
client.on("wssError", (event) => errors.push(String(event.message)));
|
|
451
|
+
client.subscribe({ agent_id: "111", name: "tag_values" });
|
|
452
|
+
// The rejection travels connect -> openSocket -> catch, which is several
|
|
453
|
+
// microtasks; draining them is not the same as advancing fake timers.
|
|
454
|
+
for (let tick = 0; tick < 5; tick += 1)
|
|
455
|
+
await Promise.resolve();
|
|
456
|
+
(0, chai_1.expect)(errors).to.have.length(1);
|
|
457
|
+
(0, chai_1.expect)(errors[0]).to.match(/dataWssUrl is empty/);
|
|
458
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances).to.have.length(0);
|
|
459
|
+
});
|
|
460
|
+
(0, mocha_1.it)("still accepts an absolute http(s) URL, which WebSocket maps itself", async () => {
|
|
461
|
+
const client = new gateway_client_1.GatewayClient({
|
|
462
|
+
dataRestUrl: "https://api.example.com",
|
|
463
|
+
controlApiUrl: "https://control.example.com",
|
|
464
|
+
dataWssUrl: "https://ws.example.com/gateway",
|
|
465
|
+
organisationId: null,
|
|
466
|
+
sharing: "none",
|
|
467
|
+
impersonateUserStorageKey: "doover:test",
|
|
468
|
+
});
|
|
469
|
+
await client.connect();
|
|
470
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances).to.have.length(1);
|
|
471
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances[0].url).to.equal("https://ws.example.com/gateway");
|
|
472
|
+
});
|
|
473
|
+
});
|
|
396
474
|
});
|
package/dist/types/common.d.ts
CHANGED
|
@@ -48,6 +48,10 @@ export interface Alarm {
|
|
|
48
48
|
id: string;
|
|
49
49
|
name: string;
|
|
50
50
|
description: string;
|
|
51
|
+
/** Channel the alarm lives on — set on every alarm the REST API returns. */
|
|
52
|
+
channel_name?: string;
|
|
53
|
+
topic_name?: string;
|
|
54
|
+
notification_policy?: "default" | "opt-in";
|
|
51
55
|
enabled: boolean;
|
|
52
56
|
key: string;
|
|
53
57
|
operator: AlarmOperator;
|
|
@@ -56,8 +60,29 @@ export interface Alarm {
|
|
|
56
60
|
expiry_mins: number | null;
|
|
57
61
|
entered_state_ts: number;
|
|
58
62
|
alarm_pending_ms: number | null;
|
|
63
|
+
rate_threshold?: number | null;
|
|
64
|
+
rate_window_ms?: number | null;
|
|
65
|
+
rate_baseline_value?: number | null;
|
|
66
|
+
rate_baseline_ts?: number | null;
|
|
67
|
+
messages?: AlarmMessages | null;
|
|
59
68
|
__source?: SourceProvenance;
|
|
60
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Per-transition notification override. `notify` and `text` are independent:
|
|
72
|
+
* turning notifications off keeps whatever wording was set, and `text: null`
|
|
73
|
+
* means "use the auto-generated message".
|
|
74
|
+
*/
|
|
75
|
+
export interface AlarmStateMessage {
|
|
76
|
+
notify?: boolean;
|
|
77
|
+
text?: string | null;
|
|
78
|
+
}
|
|
79
|
+
/** Notification overrides keyed by the state being entered. */
|
|
80
|
+
export interface AlarmMessages {
|
|
81
|
+
alarm?: AlarmStateMessage;
|
|
82
|
+
ok?: AlarmStateMessage;
|
|
83
|
+
pending?: AlarmStateMessage;
|
|
84
|
+
no_data?: AlarmStateMessage;
|
|
85
|
+
}
|
|
61
86
|
export type AlarmOperator = "eq" | "ge" | "gt" | "le" | "lt";
|
|
62
87
|
export type AlarmState = "NoData" | "OK" | "Alarm" | "AlarmPending";
|
|
63
88
|
export interface ConnectionSubscription {
|
|
@@ -84,6 +109,12 @@ export interface ConnectionDetails {
|
|
|
84
109
|
subscriptions: ConnectionSubscription[];
|
|
85
110
|
__source?: SourceProvenance;
|
|
86
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* How a subscription's `topic_filter` entries are matched. `exact` is literal
|
|
114
|
+
* (and the server's default when the field is absent); `regex` matches each
|
|
115
|
+
* expression against the whole canonical topic. Entries are ORed either way.
|
|
116
|
+
*/
|
|
117
|
+
export type NotificationTopicFilterMode = "exact" | "regex";
|
|
87
118
|
export interface NotificationSubscriptionEndpoint {
|
|
88
119
|
id: string;
|
|
89
120
|
name: string;
|
|
@@ -95,6 +126,7 @@ export interface NotificationSubscription {
|
|
|
95
126
|
subscribed_to: string;
|
|
96
127
|
severity: number;
|
|
97
128
|
topic_filter: string[];
|
|
129
|
+
topic_filter_mode: NotificationTopicFilterMode;
|
|
98
130
|
endpoints: NotificationSubscriptionEndpoint[];
|
|
99
131
|
__source?: SourceProvenance;
|
|
100
132
|
}
|
|
@@ -108,6 +140,21 @@ export interface NotificationEndpoint {
|
|
|
108
140
|
default: boolean;
|
|
109
141
|
__source?: SourceProvenance;
|
|
110
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Delivery readiness for an endpoint, without its destination address or
|
|
145
|
+
* `extra_data` — the one endpoint listing a delegated subscription manager is
|
|
146
|
+
* allowed to see.
|
|
147
|
+
*/
|
|
148
|
+
export interface NotificationEndpointSummary {
|
|
149
|
+
id: string;
|
|
150
|
+
type: number;
|
|
151
|
+
priority: number | null;
|
|
152
|
+
name: string;
|
|
153
|
+
display_name: string | null;
|
|
154
|
+
default: boolean;
|
|
155
|
+
ready: boolean;
|
|
156
|
+
__source?: SourceProvenance;
|
|
157
|
+
}
|
|
111
158
|
export interface ResourcePermission {
|
|
112
159
|
permission_id: string;
|
|
113
160
|
permission: string;
|
package/dist/types/openapi.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AgentAggregate, AgentPermission, Alarm, Aggregate, Channel, ConnectionDetails, ConnectionSubscription, ConnectionSubscriptionLog, DataSeries, MessageStructure, NotificationEndpoint, NotificationSubscription, ResourcePermission, TurnCredential } from "./common";
|
|
2
|
-
export type { AgentAggregate, AgentPermission, Alarm, Aggregate, Channel, ConnectionDetails, ConnectionSubscription, ConnectionSubscriptionLog, DataSeries, MessageStructure, NotificationEndpoint, NotificationSubscription, ResourcePermission, TurnCredential, };
|
|
1
|
+
import type { AgentAggregate, AgentPermission, Alarm, AlarmMessages, AlarmStateMessage, Aggregate, Channel, ConnectionDetails, ConnectionSubscription, ConnectionSubscriptionLog, DataSeries, MessageStructure, NotificationEndpoint, NotificationEndpointSummary, NotificationSubscription, NotificationTopicFilterMode, ResourcePermission, TurnCredential } from "./common";
|
|
2
|
+
export type { AgentAggregate, AgentPermission, Alarm, AlarmMessages, AlarmStateMessage, Aggregate, Channel, ConnectionDetails, ConnectionSubscription, ConnectionSubscriptionLog, DataSeries, MessageStructure, NotificationEndpoint, NotificationEndpointSummary, NotificationSubscription, NotificationTopicFilterMode, ResourcePermission, TurnCredential, };
|
|
3
3
|
export interface BatchMessagesResponse {
|
|
4
4
|
results: MessageStructure[];
|
|
5
5
|
count: number;
|
|
@@ -41,6 +41,9 @@ export interface NotificationDataResponse {
|
|
|
41
41
|
export interface NotificationEndpointsResponse {
|
|
42
42
|
endpoints: NotificationEndpoint[];
|
|
43
43
|
}
|
|
44
|
+
export interface NotificationEndpointSummariesResponse {
|
|
45
|
+
endpoints: NotificationEndpointSummary[];
|
|
46
|
+
}
|
|
44
47
|
export interface NotificationSubscriptionsResponse {
|
|
45
48
|
subscriptions: NotificationSubscription[];
|
|
46
49
|
}
|
|
@@ -101,10 +104,30 @@ export interface CreateAlarmRequest {
|
|
|
101
104
|
enabled?: boolean;
|
|
102
105
|
key: string;
|
|
103
106
|
operator: Alarm["operator"];
|
|
104
|
-
|
|
107
|
+
/**
|
|
108
|
+
* The threshold to compare against. Required for a threshold alarm, and
|
|
109
|
+
* must be left out entirely for a rate alarm — which states its condition
|
|
110
|
+
* with `rate_threshold` instead.
|
|
111
|
+
*/
|
|
112
|
+
value?: unknown;
|
|
105
113
|
expiry_mins?: number | null;
|
|
106
114
|
alarm_pending_ms?: number | null;
|
|
107
|
-
|
|
115
|
+
/** Stable slug used in notification topics. Derived from `name` if absent. */
|
|
116
|
+
topic_name?: string;
|
|
117
|
+
notification_policy?: Alarm["notification_policy"];
|
|
118
|
+
/**
|
|
119
|
+
* Units per second, judged over at least `rate_window_ms`. Setting the pair
|
|
120
|
+
* makes this a rate-of-change alarm; they must be set together.
|
|
121
|
+
*/
|
|
122
|
+
rate_threshold?: number | null;
|
|
123
|
+
rate_window_ms?: number | null;
|
|
124
|
+
messages?: AlarmMessages | null;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Every field is optional; each is merged rather than replaced. `messages` is
|
|
128
|
+
* merged state by state and field by field, so `{messages: {ok: {notify:
|
|
129
|
+
* false}}}` leaves the stored text alone, and an explicit `null` clears.
|
|
130
|
+
*/
|
|
108
131
|
export interface PatchAlarmRequest extends Partial<CreateAlarmRequest> {
|
|
109
132
|
}
|
|
110
133
|
export interface CreateEndpointRequest {
|
|
@@ -124,10 +147,13 @@ export interface CreateNotificationSubscriptionRequest {
|
|
|
124
147
|
endpoint_id?: string | null;
|
|
125
148
|
severity: number;
|
|
126
149
|
topic_filter: string[];
|
|
150
|
+
/** Defaults to `exact` server-side when absent. */
|
|
151
|
+
topic_filter_mode?: NotificationTopicFilterMode;
|
|
127
152
|
}
|
|
128
153
|
export interface UpdateNotificationSubscriptionRequest {
|
|
129
154
|
severity?: number;
|
|
130
155
|
topic_filter?: string[];
|
|
156
|
+
topic_filter_mode?: NotificationTopicFilterMode;
|
|
131
157
|
}
|
|
132
158
|
export interface UpdateMeWebPushEndpointRequest {
|
|
133
159
|
old_endpoint: string;
|