doover-js 0.4.0 → 0.4.2
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/gateway/gateway-client.js +1 -1
- package/dist/gateway/types.d.ts +12 -4
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +4 -1
- package/dist/react/useChannelMessage.d.ts +38 -0
- package/dist/react/useChannelMessage.js +72 -0
- package/dist/react/useChannelSubscription.d.ts +7 -2
- package/dist/react/useChannelSubscription.js +2 -2
- package/dist/test/doover-data-provider.test.js +42 -30
- package/dist/test/gateway-client.test.js +10 -6
- package/dist/test/react.test.js +80 -10
- package/dist/viewer/doover-data-provider.d.ts +7 -2
- package/dist/viewer/doover-data-provider.js +2 -2
- package/package.json +1 -1
|
@@ -243,7 +243,7 @@ class GatewayClient {
|
|
|
243
243
|
this.emit("messageCreate", (0, snowflake_1.addTimestampToMessage)(message.d));
|
|
244
244
|
break;
|
|
245
245
|
case "MessageUpdate":
|
|
246
|
-
this.emit("messageUpdate", (0, snowflake_1.addTimestampToMessage)(message.d));
|
|
246
|
+
this.emit("messageUpdate", (0, snowflake_1.addTimestampToMessage)(message.d.message), message.d.request_data);
|
|
247
247
|
break;
|
|
248
248
|
case "AggregateUpdate":
|
|
249
249
|
this.emit("aggregateUpdate", message.d);
|
package/dist/gateway/types.d.ts
CHANGED
|
@@ -38,10 +38,20 @@ export interface GatewayMessageCreate extends GatewayMessageBase {
|
|
|
38
38
|
t: "MessageCreate";
|
|
39
39
|
d: Omit<MessageStructure, "timestamp">;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* `message` is the full post-update MessageStructure; `request_data` is the
|
|
43
|
+
* diff of just-changed fields. Consumers should read `message` for the
|
|
44
|
+
* current state and only touch `request_data` if they specifically need to
|
|
45
|
+
* know what changed in this update.
|
|
46
|
+
*/
|
|
41
47
|
export interface GatewayMessageUpdate extends GatewayMessageBase {
|
|
42
48
|
op: 0;
|
|
43
49
|
t: "MessageUpdate";
|
|
44
|
-
d:
|
|
50
|
+
d: {
|
|
51
|
+
channel: ChannelRef;
|
|
52
|
+
author_id: string;
|
|
53
|
+
organisation_id?: string;
|
|
54
|
+
message: Omit<MessageStructure, "timestamp">;
|
|
45
55
|
request_data: JSONValue;
|
|
46
56
|
};
|
|
47
57
|
}
|
|
@@ -119,9 +129,7 @@ export interface GatewayListenerMap {
|
|
|
119
129
|
ready: (event: GatewayReady["d"]) => void;
|
|
120
130
|
channelSync: (event: GatewayChannelSync["d"]) => void;
|
|
121
131
|
messageCreate: (event: MessageStructure) => void;
|
|
122
|
-
messageUpdate: (
|
|
123
|
-
timestamp: number;
|
|
124
|
-
}) => void;
|
|
132
|
+
messageUpdate: (message: MessageStructure, request_data: JSONValue | undefined) => void;
|
|
125
133
|
aggregateUpdate: (event: GatewayAggregateUpdate["d"]) => void;
|
|
126
134
|
alarmTrigger: (event: GatewayAlarmTrigger["d"]) => void;
|
|
127
135
|
oneShotMessage: (event: GatewayOneShotMessage["d"]) => void;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export { useUpdateMessage } from "./useUpdateMessage";
|
|
|
15
15
|
export type { UpdateMessageVariables, UseUpdateMessageOptions, } from "./useUpdateMessage";
|
|
16
16
|
export { useChannelMessages, channelMessagesQueryKey, } from "./useChannelMessages";
|
|
17
17
|
export type { UseChannelMessagesOptions, UseChannelMessagesResult, } from "./useChannelMessages";
|
|
18
|
+
export { useChannelMessage, channelMessageQueryKey, } from "./useChannelMessage";
|
|
19
|
+
export type { UseChannelMessageOptions, UseChannelMessageResult, } from "./useChannelMessage";
|
|
18
20
|
export { useSendRpc } from "./useSendRpc";
|
|
19
21
|
export type { RpcCommandId, RpcStatusEvent, SendRpcVariables, UseSendRpcOptions, UseSendRpcResult, } from "./useSendRpc";
|
|
20
22
|
export { useMultiAgentAggregates, multiAgentAggregatesQueryKey, } from "./useMultiAgentAggregates";
|
package/dist/react/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resetSharedQueryClient = exports.getSharedQueryClient = exports.useTurnCredentials = exports.multiAgentChannelMessagesQueryKey = exports.useMultiAgentChannelMessages = exports.multiAgentAggregatesQueryKey = exports.useMultiAgentAggregates = exports.useSendRpc = exports.channelMessagesQueryKey = exports.useChannelMessages = exports.useUpdateMessage = exports.useUpdateAggregate = exports.useSendMessage = exports.useAgentChannel = exports.channelAggregateQueryKey = exports.useChannelAggregate = exports.useChannelSubscription = exports.agentConnectionsQueryKey = exports.useAgentConnections = exports.useConnectionState = exports.useDooverClient = exports.DooverProvider = void 0;
|
|
3
|
+
exports.resetSharedQueryClient = exports.getSharedQueryClient = exports.useTurnCredentials = exports.multiAgentChannelMessagesQueryKey = exports.useMultiAgentChannelMessages = exports.multiAgentAggregatesQueryKey = exports.useMultiAgentAggregates = exports.useSendRpc = exports.channelMessageQueryKey = exports.useChannelMessage = exports.channelMessagesQueryKey = exports.useChannelMessages = exports.useUpdateMessage = exports.useUpdateAggregate = exports.useSendMessage = exports.useAgentChannel = exports.channelAggregateQueryKey = exports.useChannelAggregate = exports.useChannelSubscription = exports.agentConnectionsQueryKey = exports.useAgentConnections = exports.useConnectionState = exports.useDooverClient = exports.DooverProvider = void 0;
|
|
4
4
|
var context_1 = require("./context");
|
|
5
5
|
Object.defineProperty(exports, "DooverProvider", { enumerable: true, get: function () { return context_1.DooverProvider; } });
|
|
6
6
|
Object.defineProperty(exports, "useDooverClient", { enumerable: true, get: function () { return context_1.useDooverClient; } });
|
|
@@ -25,6 +25,9 @@ Object.defineProperty(exports, "useUpdateMessage", { enumerable: true, get: func
|
|
|
25
25
|
var useChannelMessages_1 = require("./useChannelMessages");
|
|
26
26
|
Object.defineProperty(exports, "useChannelMessages", { enumerable: true, get: function () { return useChannelMessages_1.useChannelMessages; } });
|
|
27
27
|
Object.defineProperty(exports, "channelMessagesQueryKey", { enumerable: true, get: function () { return useChannelMessages_1.channelMessagesQueryKey; } });
|
|
28
|
+
var useChannelMessage_1 = require("./useChannelMessage");
|
|
29
|
+
Object.defineProperty(exports, "useChannelMessage", { enumerable: true, get: function () { return useChannelMessage_1.useChannelMessage; } });
|
|
30
|
+
Object.defineProperty(exports, "channelMessageQueryKey", { enumerable: true, get: function () { return useChannelMessage_1.channelMessageQueryKey; } });
|
|
28
31
|
var useSendRpc_1 = require("./useSendRpc");
|
|
29
32
|
Object.defineProperty(exports, "useSendRpc", { enumerable: true, get: function () { return useSendRpc_1.useSendRpc; } });
|
|
30
33
|
var useMultiAgentAggregates_1 = require("./useMultiAgentAggregates");
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type UseQueryResult } from "@tanstack/react-query";
|
|
2
|
+
import type { JSONValue, MessageAttachment, MessageStructure } from "../types/common";
|
|
3
|
+
import type { ChannelIdentifier } from "../types/viewer";
|
|
4
|
+
export declare function channelMessageQueryKey(agentId: string | undefined, channelName: string | undefined, messageId: string | undefined): readonly ["doover", "agent", string | undefined, "channel", string | undefined, "message", string | undefined];
|
|
5
|
+
export interface UseChannelMessageOptions {
|
|
6
|
+
/** If false, skip subscribing for live message-update events. Defaults true. */
|
|
7
|
+
liveUpdates?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Shape returned by `useChannelMessage`. Mirrors `UseChannelAggregateResult`:
|
|
11
|
+
* `data` is unwrapped to the message payload (`message.data`) so the common
|
|
12
|
+
* `const { data } = useChannelMessage(…)` pattern works without a secondary
|
|
13
|
+
* access. The full `MessageStructure` and a couple of useful slots
|
|
14
|
+
* (`attachments`, `timestamp`) are hoisted alongside.
|
|
15
|
+
*/
|
|
16
|
+
export interface UseChannelMessageResult<TData> extends Omit<UseQueryResult<MessageStructure<TData> | undefined>, "data"> {
|
|
17
|
+
/** Unwrapped message payload (`message.data`). Undefined until first fetch. */
|
|
18
|
+
data: TData | undefined;
|
|
19
|
+
/** Full message structure, or undefined until first fetch. */
|
|
20
|
+
message: MessageStructure<TData> | undefined;
|
|
21
|
+
attachments: MessageAttachment[] | undefined;
|
|
22
|
+
/** Server timestamp reported by the message. */
|
|
23
|
+
timestamp: number | undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Fetch a single channel message by id and keep it live via gateway
|
|
27
|
+
* `MessageUpdate` events. Mirrors `useChannelAggregate`'s pattern: REST seeds
|
|
28
|
+
* the cache, then `onMessageUpdate` patches it whenever the message body is
|
|
29
|
+
* mutated server-side — handy for watching long-running RPC-style messages
|
|
30
|
+
* where the receiver writes progress into a `response` field.
|
|
31
|
+
*
|
|
32
|
+
* Channel subscriptions deliver every message in the channel, so the
|
|
33
|
+
* handlers filter to the requested `messageId` before patching the cache.
|
|
34
|
+
*
|
|
35
|
+
* Typed on `TData` — the shape of `message.data`. Defaults to `JSONValue` to
|
|
36
|
+
* match `MessageStructure`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function useChannelMessage<TData = JSONValue>(identifier: ChannelIdentifier, messageId: string | undefined, options?: UseChannelMessageOptions): UseChannelMessageResult<TData>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.channelMessageQueryKey = channelMessageQueryKey;
|
|
4
|
+
exports.useChannelMessage = useChannelMessage;
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const react_query_1 = require("@tanstack/react-query");
|
|
7
|
+
const context_1 = require("./context");
|
|
8
|
+
const useChannelSubscription_1 = require("./useChannelSubscription");
|
|
9
|
+
function channelMessageQueryKey(agentId, channelName, messageId) {
|
|
10
|
+
return [
|
|
11
|
+
"doover",
|
|
12
|
+
"agent",
|
|
13
|
+
agentId,
|
|
14
|
+
"channel",
|
|
15
|
+
channelName,
|
|
16
|
+
"message",
|
|
17
|
+
messageId,
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Fetch a single channel message by id and keep it live via gateway
|
|
22
|
+
* `MessageUpdate` events. Mirrors `useChannelAggregate`'s pattern: REST seeds
|
|
23
|
+
* the cache, then `onMessageUpdate` patches it whenever the message body is
|
|
24
|
+
* mutated server-side — handy for watching long-running RPC-style messages
|
|
25
|
+
* where the receiver writes progress into a `response` field.
|
|
26
|
+
*
|
|
27
|
+
* Channel subscriptions deliver every message in the channel, so the
|
|
28
|
+
* handlers filter to the requested `messageId` before patching the cache.
|
|
29
|
+
*
|
|
30
|
+
* Typed on `TData` — the shape of `message.data`. Defaults to `JSONValue` to
|
|
31
|
+
* match `MessageStructure`.
|
|
32
|
+
*/
|
|
33
|
+
function useChannelMessage(identifier, messageId, options) {
|
|
34
|
+
const client = (0, context_1.useDooverClient)();
|
|
35
|
+
const queryClient = (0, react_query_1.useQueryClient)();
|
|
36
|
+
const { agentId, channelName } = identifier;
|
|
37
|
+
const liveUpdates = options?.liveUpdates ?? true;
|
|
38
|
+
const key = channelMessageQueryKey(agentId, channelName, messageId);
|
|
39
|
+
const onMessageUpdate = (0, react_1.useCallback)((message) => {
|
|
40
|
+
if (message.id !== messageId)
|
|
41
|
+
return;
|
|
42
|
+
queryClient.setQueryData(key, message);
|
|
43
|
+
},
|
|
44
|
+
// The key array is structurally stable per (agentId, channelName, messageId).
|
|
45
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
46
|
+
[queryClient, agentId, channelName, messageId]);
|
|
47
|
+
// Cover the case where the caller has the id before the create event has
|
|
48
|
+
// landed on this client — patch the cache when it shows up.
|
|
49
|
+
const onMessage = (0, react_1.useCallback)((message) => {
|
|
50
|
+
if (message.id !== messageId)
|
|
51
|
+
return;
|
|
52
|
+
queryClient.setQueryData(key, message);
|
|
53
|
+
},
|
|
54
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
55
|
+
[queryClient, agentId, channelName, messageId]);
|
|
56
|
+
(0, useChannelSubscription_1.useChannelSubscription)(liveUpdates && messageId ? identifier : undefined, { onMessage, onMessageUpdate });
|
|
57
|
+
const query = (0, react_query_1.useQuery)({
|
|
58
|
+
queryKey: key,
|
|
59
|
+
enabled: !!agentId && !!channelName && !!messageId,
|
|
60
|
+
staleTime: Infinity,
|
|
61
|
+
queryFn: () => client.messages.getMessage(agentId, channelName, messageId),
|
|
62
|
+
});
|
|
63
|
+
const message = query.data;
|
|
64
|
+
const { data: _ignored, ...rest } = query;
|
|
65
|
+
return {
|
|
66
|
+
...rest,
|
|
67
|
+
data: message?.data,
|
|
68
|
+
message,
|
|
69
|
+
attachments: message?.attachments,
|
|
70
|
+
timestamp: message?.timestamp,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import type { Aggregate, MessageStructure } from "../types/common";
|
|
1
|
+
import type { Aggregate, JSONValue, MessageStructure } from "../types/common";
|
|
2
2
|
import type { ChannelIdentifier } from "../types/viewer";
|
|
3
3
|
export interface ChannelSubscriptionHandlers {
|
|
4
4
|
onMessage?: (message: MessageStructure) => void;
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Fires on server-side MessageUpdate events. `message` is the full updated
|
|
7
|
+
* MessageStructure; `request_data` (optional) is the diff of what changed
|
|
8
|
+
* in this specific update. Most consumers only need `message`.
|
|
9
|
+
*/
|
|
10
|
+
onMessageUpdate?: (message: MessageStructure, request_data?: JSONValue) => void;
|
|
6
11
|
onAggregate?: (aggregate: Aggregate) => void;
|
|
7
12
|
}
|
|
8
13
|
/**
|
|
@@ -24,8 +24,8 @@ function useChannelSubscription(identifier, handlers) {
|
|
|
24
24
|
const aggregateCallback = (_id, aggregate) => {
|
|
25
25
|
handlersRef.current.onAggregate?.(aggregate);
|
|
26
26
|
};
|
|
27
|
-
const messageUpdateCallback = (_id, message) => {
|
|
28
|
-
handlersRef.current.onMessageUpdate?.(message);
|
|
27
|
+
const messageUpdateCallback = (_id, message, request_data) => {
|
|
28
|
+
handlersRef.current.onMessageUpdate?.(message, request_data);
|
|
29
29
|
};
|
|
30
30
|
void client.viewer.subscribeToChannel(identifier, messageCallback, aggregateCallback, messageUpdateCallback);
|
|
31
31
|
return () => {
|
|
@@ -289,36 +289,44 @@ const helpers_1 = require("./helpers");
|
|
|
289
289
|
op: 0,
|
|
290
290
|
t: "MessageUpdate",
|
|
291
291
|
d: {
|
|
292
|
-
id: rpcId,
|
|
293
|
-
author_id: "u1",
|
|
294
292
|
channel: { agent_id: "a1", name: "c1" },
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
293
|
+
author_id: "u1",
|
|
294
|
+
message: {
|
|
295
|
+
id: rpcId,
|
|
296
|
+
author_id: "u1",
|
|
297
|
+
channel: { agent_id: "a1", name: "c1" },
|
|
298
|
+
data: {
|
|
299
|
+
type: "rpc",
|
|
300
|
+
method: "ping",
|
|
301
|
+
request: {},
|
|
302
|
+
status: { code: "acknowledged", message: { timestamp: 1 } },
|
|
303
|
+
response: {},
|
|
304
|
+
},
|
|
305
|
+
attachments: [],
|
|
301
306
|
},
|
|
302
|
-
|
|
303
|
-
request_data: {},
|
|
307
|
+
request_data: { status: { code: "acknowledged", message: { timestamp: 1 } } },
|
|
304
308
|
},
|
|
305
309
|
});
|
|
306
310
|
ws.receive({
|
|
307
311
|
op: 0,
|
|
308
312
|
t: "MessageUpdate",
|
|
309
313
|
d: {
|
|
310
|
-
id: rpcId,
|
|
311
|
-
author_id: "u1",
|
|
312
314
|
channel: { agent_id: "a1", name: "c1" },
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
315
|
+
author_id: "u1",
|
|
316
|
+
message: {
|
|
317
|
+
id: rpcId,
|
|
318
|
+
author_id: "u1",
|
|
319
|
+
channel: { agent_id: "a1", name: "c1" },
|
|
320
|
+
data: {
|
|
321
|
+
type: "rpc",
|
|
322
|
+
method: "ping",
|
|
323
|
+
request: {},
|
|
324
|
+
status: { code: "success" },
|
|
325
|
+
response: { pong: true },
|
|
326
|
+
},
|
|
327
|
+
attachments: [],
|
|
319
328
|
},
|
|
320
|
-
|
|
321
|
-
request_data: {},
|
|
329
|
+
request_data: { status: { code: "success" }, response: { pong: true } },
|
|
322
330
|
},
|
|
323
331
|
});
|
|
324
332
|
await (0, chai_1.expect)(promise).to.eventually.deep.equal({ pong: true });
|
|
@@ -366,18 +374,22 @@ const helpers_1 = require("./helpers");
|
|
|
366
374
|
op: 0,
|
|
367
375
|
t: "MessageUpdate",
|
|
368
376
|
d: {
|
|
369
|
-
id: rpcId,
|
|
370
|
-
author_id: "u1",
|
|
371
377
|
channel: { agent_id: "a1", name: "c1" },
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
+
author_id: "u1",
|
|
379
|
+
message: {
|
|
380
|
+
id: rpcId,
|
|
381
|
+
author_id: "u1",
|
|
382
|
+
channel: { agent_id: "a1", name: "c1" },
|
|
383
|
+
data: {
|
|
384
|
+
type: "rpc",
|
|
385
|
+
method: "ping",
|
|
386
|
+
request: {},
|
|
387
|
+
status: { code: "error", message: "boom" },
|
|
388
|
+
response: {},
|
|
389
|
+
},
|
|
390
|
+
attachments: [],
|
|
378
391
|
},
|
|
379
|
-
|
|
380
|
-
request_data: {},
|
|
392
|
+
request_data: { status: { code: "error", message: "boom" } },
|
|
381
393
|
},
|
|
382
394
|
});
|
|
383
395
|
await (0, chai_1.expect)(promise).to.eventually.be.rejectedWith("boom");
|
|
@@ -115,12 +115,16 @@ const helpers_1 = require("./helpers");
|
|
|
115
115
|
op: 0,
|
|
116
116
|
t: "MessageUpdate",
|
|
117
117
|
d: {
|
|
118
|
-
id,
|
|
119
|
-
author_id: "u1",
|
|
120
118
|
channel: { agent_id: "a1", name: "c1" },
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
author_id: "u1",
|
|
120
|
+
message: {
|
|
121
|
+
id,
|
|
122
|
+
author_id: "u1",
|
|
123
|
+
channel: { agent_id: "a1", name: "c1" },
|
|
124
|
+
data: { full: true },
|
|
125
|
+
attachments: [],
|
|
126
|
+
},
|
|
127
|
+
request_data: { diff: true },
|
|
124
128
|
},
|
|
125
129
|
});
|
|
126
130
|
ws.receive({
|
|
@@ -195,7 +199,7 @@ const helpers_1 = require("./helpers");
|
|
|
195
199
|
});
|
|
196
200
|
(0, chai_1.expect)(channelSync.calledOnce).to.equal(true);
|
|
197
201
|
(0, chai_1.expect)(messageCreate.calledWithMatch(sinon_1.default.match({ id, timestamp: sinon_1.default.match.number }))).to.equal(true);
|
|
198
|
-
(0, chai_1.expect)(messageUpdate.calledWithMatch(sinon_1.default.match({ id, timestamp: sinon_1.default.match.number }))).to.equal(true);
|
|
202
|
+
(0, chai_1.expect)(messageUpdate.calledWithMatch(sinon_1.default.match({ id, data: { full: true }, timestamp: sinon_1.default.match.number }), sinon_1.default.match({ diff: true }))).to.equal(true);
|
|
199
203
|
(0, chai_1.expect)(aggregateUpdate.calledOnce).to.equal(true);
|
|
200
204
|
(0, chai_1.expect)(alarmTrigger.calledOnce).to.equal(true);
|
|
201
205
|
(0, chai_1.expect)(oneShot.calledOnce).to.equal(true);
|
package/dist/test/react.test.js
CHANGED
|
@@ -270,6 +270,72 @@ function wrapper(client) {
|
|
|
270
270
|
liveId,
|
|
271
271
|
]));
|
|
272
272
|
});
|
|
273
|
+
(0, mocha_1.it)("useChannelMessage seeds via REST and patches on MessageUpdate", async () => {
|
|
274
|
+
const messageId = (0, snowflake_1.generateSnowflakeIdAtTime)(new Date("2026-01-01T00:00:00.000Z"));
|
|
275
|
+
const otherId = (0, snowflake_1.generateSnowflakeIdAtTime)(new Date("2026-01-01T00:00:01.000Z"));
|
|
276
|
+
const fetchMock = (0, helpers_1.createFetchMock)(() => (0, helpers_1.createJsonResponse)({
|
|
277
|
+
id: messageId,
|
|
278
|
+
author_id: "u1",
|
|
279
|
+
channel: { agent_id: "a1", name: "rpc" },
|
|
280
|
+
data: { method: "ping", response: { status: "pulling" } },
|
|
281
|
+
attachments: [],
|
|
282
|
+
}));
|
|
283
|
+
const client = new doover_client_1.DooverClient({
|
|
284
|
+
dataRestUrl: "https://api.example.com",
|
|
285
|
+
controlApiUrl: "https://control.example.com",
|
|
286
|
+
dataWssUrl: "wss://ws.example.com",
|
|
287
|
+
fetchImpl: fetchMock,
|
|
288
|
+
webSocketImpl: helpers_1.MockWebSocket,
|
|
289
|
+
disableBrowserLifecycleHooks: true,
|
|
290
|
+
});
|
|
291
|
+
const { result } = (0, react_1.renderHook)(() => (0, react_2.useChannelMessage)({ agentId: "a1", channelName: "rpc" }, messageId), { wrapper: wrapper(client) });
|
|
292
|
+
await (0, react_1.waitFor)(() => (0, chai_1.expect)(result.current.isSuccess).to.equal(true));
|
|
293
|
+
(0, chai_1.expect)(result.current.data?.response?.status).to.equal("pulling");
|
|
294
|
+
(0, chai_1.expect)(result.current.message?.id).to.equal(messageId);
|
|
295
|
+
await (0, react_1.act)(async () => {
|
|
296
|
+
const ws = helpers_1.MockWebSocket.instances[0];
|
|
297
|
+
ws.open();
|
|
298
|
+
ws.receive({ op: 0, t: "Hello", d: {} });
|
|
299
|
+
ws.receive({
|
|
300
|
+
op: 0,
|
|
301
|
+
t: "Ready",
|
|
302
|
+
d: { session_id: "s1", session_token: "t1", subscriptions: [] },
|
|
303
|
+
});
|
|
304
|
+
// Update for an unrelated message — should be ignored.
|
|
305
|
+
ws.receive({
|
|
306
|
+
op: 0,
|
|
307
|
+
t: "MessageUpdate",
|
|
308
|
+
d: {
|
|
309
|
+
channel: { agent_id: "a1", name: "rpc" },
|
|
310
|
+
author_id: "u1",
|
|
311
|
+
message: {
|
|
312
|
+
id: otherId,
|
|
313
|
+
author_id: "u1",
|
|
314
|
+
channel: { agent_id: "a1", name: "rpc" },
|
|
315
|
+
data: { method: "ping", response: { status: "success" } },
|
|
316
|
+
attachments: [],
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
// Update for the message we're watching — should patch.
|
|
321
|
+
ws.receive({
|
|
322
|
+
op: 0,
|
|
323
|
+
t: "MessageUpdate",
|
|
324
|
+
d: {
|
|
325
|
+
channel: { agent_id: "a1", name: "rpc" },
|
|
326
|
+
author_id: "u1",
|
|
327
|
+
message: {
|
|
328
|
+
id: messageId,
|
|
329
|
+
author_id: "u1",
|
|
330
|
+
channel: { agent_id: "a1", name: "rpc" },
|
|
331
|
+
data: { method: "ping", response: { status: "success" } },
|
|
332
|
+
attachments: [],
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
await (0, react_1.waitFor)(() => (0, chai_1.expect)(result.current.data?.response?.status).to.equal("success"));
|
|
338
|
+
});
|
|
273
339
|
(0, mocha_1.it)("getSharedQueryClient returns the same QueryClient across callers", () => {
|
|
274
340
|
(0, react_2.resetSharedQueryClient)();
|
|
275
341
|
const a = (0, react_2.getSharedQueryClient)();
|
|
@@ -328,18 +394,22 @@ function wrapper(client) {
|
|
|
328
394
|
op: 0,
|
|
329
395
|
t: "MessageUpdate",
|
|
330
396
|
d: {
|
|
331
|
-
id: rpcId,
|
|
332
|
-
author_id: "u1",
|
|
333
397
|
channel: { agent_id: "a1", name: "ui_cmds" },
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
398
|
+
author_id: "u1",
|
|
399
|
+
message: {
|
|
400
|
+
id: rpcId,
|
|
401
|
+
author_id: "u1",
|
|
402
|
+
channel: { agent_id: "a1", name: "ui_cmds" },
|
|
403
|
+
data: {
|
|
404
|
+
type: "rpc",
|
|
405
|
+
method: "ping",
|
|
406
|
+
request: {},
|
|
407
|
+
status: { code: "success" },
|
|
408
|
+
response: { pong: true },
|
|
409
|
+
},
|
|
410
|
+
attachments: [],
|
|
340
411
|
},
|
|
341
|
-
|
|
342
|
-
request_data: {},
|
|
412
|
+
request_data: { status: { code: "success" }, response: { pong: true } },
|
|
343
413
|
},
|
|
344
414
|
});
|
|
345
415
|
return promise;
|
|
@@ -6,12 +6,17 @@ import { MessagesApi, type ListMessagesParams } from "../apis/messages-api";
|
|
|
6
6
|
import type { DooverAuth } from "../auth/doover-auth";
|
|
7
7
|
import { GatewayClient } from "../gateway/gateway-client";
|
|
8
8
|
import { RestClient, type DooverClientConfig } from "../http/rest-client";
|
|
9
|
-
import type { Aggregate, Channel, ConnectionDetails, MessageStructure, RpcRequest, RpcStatus } from "../types/common";
|
|
9
|
+
import type { Aggregate, Channel, ConnectionDetails, JSONValue, MessageStructure, RpcRequest, RpcStatus } from "../types/common";
|
|
10
10
|
import type { Agent, AgentsResponse, ChannelIdentifier, ChannelsDataProvider, SubscriptionDetails, User } from "../types/viewer";
|
|
11
11
|
interface SubscriptionEntry {
|
|
12
12
|
messageCallback: (identifier: ChannelIdentifier, message: MessageStructure) => void;
|
|
13
13
|
aggregateCallback: (identifier: ChannelIdentifier, aggregate: Aggregate) => void;
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* `message` is the full post-update MessageStructure (server-side `.message`
|
|
16
|
+
* field). `request_data` is the diff of just-changed fields, or undefined
|
|
17
|
+
* if the server didn't provide one. Most consumers only need `message`.
|
|
18
|
+
*/
|
|
19
|
+
messageUpdateCallback?: (identifier: ChannelIdentifier, message: MessageStructure, request_data?: JSONValue) => void;
|
|
15
20
|
}
|
|
16
21
|
export declare class DooverDataProvider implements ChannelsDataProvider<ChannelIdentifier> {
|
|
17
22
|
private readonly config;
|
|
@@ -34,10 +34,10 @@ class DooverDataProvider {
|
|
|
34
34
|
const identifier = this.toIdentifier(message.channel.agent_id, message.channel.name);
|
|
35
35
|
this.subscriptions.get(key)?.forEach((entry) => entry.messageCallback(identifier, message));
|
|
36
36
|
});
|
|
37
|
-
this.gateway.on("messageUpdate", (message) => {
|
|
37
|
+
this.gateway.on("messageUpdate", (message, request_data) => {
|
|
38
38
|
const key = this.channelKey(message.channel.agent_id, message.channel.name);
|
|
39
39
|
const identifier = this.toIdentifier(message.channel.agent_id, message.channel.name);
|
|
40
|
-
this.subscriptions.get(key)?.forEach((entry) => entry.messageUpdateCallback?.(identifier, message));
|
|
40
|
+
this.subscriptions.get(key)?.forEach((entry) => entry.messageUpdateCallback?.(identifier, message, request_data));
|
|
41
41
|
});
|
|
42
42
|
this.gateway.on("channelSync", (event) => {
|
|
43
43
|
const key = this.channelKey(event.channel.agent_id, event.channel.name);
|