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.
@@ -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;
@@ -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
  }
@@ -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(10);
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
- (0, chai_1.expect)(fetchMock.callCount).to.equal(21);
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
  });
@@ -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;
@@ -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
- value: unknown;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doover-js",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "TypeScript client for Doover.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",