@meshagent/meshagent-react 0.38.1 → 0.38.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +78 -41
  3. package/dist/cjs/client-toolkits.d.ts +2 -2
  4. package/dist/cjs/client-toolkits.js +143 -10
  5. package/dist/cjs/document-connection-scope.d.ts +7 -12
  6. package/dist/cjs/document-connection-scope.js +88 -82
  7. package/dist/cjs/index.d.ts +3 -2
  8. package/dist/cjs/index.js +3 -2
  9. package/dist/cjs/livekit-client.d.ts +23 -0
  10. package/dist/cjs/livekit-client.js +66 -0
  11. package/dist/cjs/livekit-protocol.d.ts +21 -0
  12. package/dist/cjs/livekit-protocol.js +97 -0
  13. package/dist/cjs/room-connection-scope.d.ts +16 -16
  14. package/dist/cjs/room-connection-scope.js +207 -90
  15. package/dist/cjs/room-participants.d.ts +2 -0
  16. package/dist/cjs/room-participants.js +46 -0
  17. package/dist/esm/client-toolkits.d.ts +2 -2
  18. package/dist/esm/client-toolkits.js +145 -12
  19. package/dist/esm/document-connection-scope.d.ts +7 -12
  20. package/dist/esm/document-connection-scope.js +88 -81
  21. package/dist/esm/index.d.ts +3 -2
  22. package/dist/esm/index.js +3 -2
  23. package/dist/esm/livekit-client.d.ts +23 -0
  24. package/dist/esm/livekit-client.js +61 -0
  25. package/dist/esm/livekit-protocol.d.ts +21 -0
  26. package/dist/esm/livekit-protocol.js +60 -0
  27. package/dist/esm/room-connection-scope.d.ts +16 -16
  28. package/dist/esm/room-connection-scope.js +205 -89
  29. package/dist/esm/room-participants.d.ts +2 -0
  30. package/dist/esm/room-participants.js +43 -0
  31. package/package.json +2 -2
  32. package/dist/cjs/chat.d.ts +0 -33
  33. package/dist/cjs/chat.js +0 -207
  34. package/dist/cjs/file-upload.d.ts +0 -43
  35. package/dist/cjs/file-upload.js +0 -168
  36. package/dist/esm/chat.d.ts +0 -33
  37. package/dist/esm/chat.js +0 -201
  38. package/dist/esm/file-upload.d.ts +0 -43
  39. package/dist/esm/file-upload.js +0 -163
@@ -1,7 +1,14 @@
1
- import { useEffect, useState, useRef } from 'react';
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { ParticipantToken, RoomClient, RoomMessageEvent, RoomServerException, WebSocketClientProtocol, } from '@meshagent/meshagent';
2
3
  import { subscribe } from './subscribe-async-gen';
3
- import { RoomMessageEvent } from '@meshagent/meshagent';
4
- import { ParticipantToken, RoomClient, WebSocketClientProtocol, } from '@meshagent/meshagent';
4
+ const retryBaseDelayMs = 500;
5
+ const retryMaxDelayMs = 30000;
6
+ function getRetryDelayMs(retryCount) {
7
+ return Math.min(retryMaxDelayMs, retryBaseDelayMs * 2 ** retryCount);
8
+ }
9
+ function isRetryableConnectionError(error) {
10
+ return error instanceof RoomServerException && error.retryable;
11
+ }
5
12
  /* -------------------------------------------------
6
13
  * Authorization helpers
7
14
  * ------------------------------------------------- */
@@ -14,71 +21,169 @@ export const developmentAuthorization = ({ url, projectId, apiKeyId, participant
14
21
  token.addRoomGrant(roomName);
15
22
  token.addRoleGrant('user');
16
23
  const jwt = await token.toJwt({ token: secret });
17
- return { url, jwt };
24
+ return {
25
+ jwt,
26
+ projectId,
27
+ roomName,
28
+ roomUrl: url,
29
+ };
18
30
  };
19
- export function useRoomConnection(props) {
20
- const { authorization, enableMessaging = true } = props;
31
+ export const staticAuthorization = ({ projectId, roomName, url, jwt, }) => async () => ({
32
+ jwt,
33
+ projectId,
34
+ roomName,
35
+ roomUrl: url,
36
+ });
37
+ export function useRoomConnection({ reconnectKey, authorization, enableMessaging = true, onReady, oauthTokenRequestHandler, secretRequestHandler, roomClientFactory, }) {
21
38
  const [client, setClient] = useState(null);
22
39
  const [ready, setReady] = useState(false);
23
40
  const [state, setState] = useState('authorizing');
24
41
  const [error, setError] = useState(null);
25
- // Keep the latest client in a ref so we can call `dispose` in cleanup.
26
42
  const clientRef = useRef(null);
27
- clientRef.current = client;
28
- // Instance method exposed to consumers (rarely needed).
29
- const dispose = () => {
30
- clientRef.current?.dispose();
31
- setState('done');
32
- };
43
+ const cancelConnectionRef = useRef(() => { });
33
44
  useEffect(() => {
34
45
  let cancelled = false;
35
- const connect = async () => {
36
- try {
37
- // 1️⃣ Get connection credentials
38
- const { url, jwt } = await authorization();
39
- if (cancelled)
46
+ let retryTimeout = null;
47
+ const clearRetryTimeout = () => {
48
+ if (retryTimeout != null) {
49
+ clearTimeout(retryTimeout);
50
+ retryTimeout = null;
51
+ }
52
+ };
53
+ const disposeClient = (room) => {
54
+ if (room == null) {
55
+ return;
56
+ }
57
+ if (clientRef.current === room) {
58
+ clientRef.current = null;
59
+ }
60
+ room.dispose();
61
+ };
62
+ const waitForRetry = (delayMs) => new Promise((resolve) => {
63
+ retryTimeout = setTimeout(() => {
64
+ retryTimeout = null;
65
+ resolve();
66
+ }, delayMs);
67
+ });
68
+ setClient(null);
69
+ setReady(false);
70
+ const cancelConnection = ({ updateState }) => {
71
+ if (cancelled) {
72
+ return;
73
+ }
74
+ cancelled = true;
75
+ clearRetryTimeout();
76
+ const currentClient = clientRef.current;
77
+ clientRef.current = null;
78
+ disposeClient(currentClient);
79
+ if (updateState) {
80
+ setClient(null);
81
+ setReady(false);
82
+ }
83
+ };
84
+ cancelConnectionRef.current = () => cancelConnection({ updateState: true });
85
+ void (async () => {
86
+ let retryCount = 0;
87
+ while (!cancelled) {
88
+ let connectionInfo;
89
+ try {
90
+ setState('authorizing');
91
+ setError(null);
92
+ connectionInfo = await authorization();
93
+ }
94
+ catch (nextError) {
95
+ if (cancelled) {
96
+ return;
97
+ }
98
+ setError(nextError);
99
+ setState('done');
100
+ setReady(false);
40
101
  return;
41
- const room = new RoomClient({
42
- protocolFactory: WebSocketClientProtocol.createFactory({ url, token: jwt }),
43
- });
44
- setClient(room);
45
- setState('connecting');
46
- await room.start({
47
- onDone: () => {
48
- if (cancelled)
49
- return;
50
- setState('done');
51
- },
52
- onError: (e) => {
53
- if (cancelled)
54
- return;
55
- setError(e);
56
- setState('done');
57
- },
58
- });
59
- if (enableMessaging) {
60
- room.messaging.enable();
61
102
  }
62
- if (cancelled)
103
+ if (cancelled) {
63
104
  return;
64
- setState('ready');
65
- setReady(true);
66
- }
67
- catch (e) {
68
- if (cancelled)
105
+ }
106
+ let nextClient;
107
+ if (roomClientFactory != null) {
108
+ nextClient = roomClientFactory(connectionInfo);
109
+ }
110
+ else {
111
+ nextClient = new RoomClient({
112
+ protocolFactory: WebSocketClientProtocol.createFactory({
113
+ url: connectionInfo.roomUrl,
114
+ token: connectionInfo.jwt,
115
+ }),
116
+ oauthTokenRequestHandler: oauthTokenRequestHandler == null
117
+ ? undefined
118
+ : (request) => oauthTokenRequestHandler(nextClient, request),
119
+ secretRequestHandler: secretRequestHandler == null
120
+ ? undefined
121
+ : (request) => secretRequestHandler(nextClient, request),
122
+ });
123
+ }
124
+ clientRef.current = nextClient;
125
+ setClient(nextClient);
126
+ setReady(false);
127
+ setState('connecting');
128
+ try {
129
+ await nextClient.start({
130
+ onDone: () => {
131
+ if (cancelled || clientRef.current !== nextClient) {
132
+ return;
133
+ }
134
+ setReady(false);
135
+ setState('done');
136
+ },
137
+ onError: (nextError) => {
138
+ if (cancelled || clientRef.current !== nextClient) {
139
+ return;
140
+ }
141
+ setError(nextError);
142
+ setReady(false);
143
+ setState('done');
144
+ },
145
+ });
146
+ if (enableMessaging) {
147
+ nextClient.messaging.enable();
148
+ }
149
+ if (cancelled || clientRef.current !== nextClient) {
150
+ disposeClient(nextClient);
151
+ return;
152
+ }
153
+ retryCount = 0;
154
+ setError(null);
155
+ setReady(true);
156
+ setState('ready');
157
+ onReady?.(nextClient);
69
158
  return;
70
- setError(e);
71
- setState('done');
159
+ }
160
+ catch (nextError) {
161
+ disposeClient(nextClient);
162
+ if (cancelled) {
163
+ return;
164
+ }
165
+ setClient((currentClient) => currentClient === nextClient ? null : currentClient);
166
+ setReady(false);
167
+ setError(nextError);
168
+ if (!isRetryableConnectionError(nextError)) {
169
+ setState('done');
170
+ return;
171
+ }
172
+ setState('retrying');
173
+ await waitForRetry(getRetryDelayMs(retryCount));
174
+ retryCount += 1;
175
+ }
72
176
  }
73
- };
74
- connect();
177
+ })();
75
178
  return () => {
76
- // React unmount or deps change → cancel & dispose
77
- cancelled = true;
78
- dispose();
179
+ cancelConnection({ updateState: false });
180
+ cancelConnectionRef.current = () => { };
79
181
  };
80
- // eslint‑disable‑next‑line react-hooks/exhaustive-deps
81
- }, []); // run once, just like componentDidMount
182
+ }, [reconnectKey, enableMessaging, roomClientFactory]);
183
+ const dispose = () => {
184
+ cancelConnectionRef.current();
185
+ setState('done');
186
+ };
82
187
  return {
83
188
  client,
84
189
  state,
@@ -94,48 +199,59 @@ export function useRoomIndicators({ room, path }) {
94
199
  const [typing, setTyping] = useState(false);
95
200
  const [thinking, setThinking] = useState(false);
96
201
  useEffect(() => {
97
- if (!room)
202
+ if (!room) {
98
203
  return;
99
- const s = subscribe(room.listen(), {
204
+ }
205
+ setTyping(false);
206
+ setThinking(false);
207
+ const subscription = subscribe(room.listen(), {
100
208
  next: (event) => {
101
- if (event instanceof RoomMessageEvent) {
102
- const { message } = event;
103
- // Ignore messages from ourselves
104
- if (message.fromParticipantId === room.localParticipant?.id) {
105
- return;
106
- }
107
- // Ignore messages not for this path
108
- if (message.message.path !== path) {
109
- return;
110
- }
111
- if (message.type === "typing") {
112
- // Clear any existing timer for this participant
113
- clearTimeout(typingMap.current[message.fromParticipantId]);
114
- // Set a new timer to remove typing after 1 second
115
- typingMap.current[message.fromParticipantId] = setTimeout(() => {
116
- delete typingMap.current[message.fromParticipantId];
117
- setTyping(Object.keys(typingMap.current).length > 0);
118
- }, 1000);
119
- // Update typing state
209
+ if (!(event instanceof RoomMessageEvent)) {
210
+ return;
211
+ }
212
+ const { message } = event;
213
+ if (message.fromParticipantId === room.localParticipant?.id) {
214
+ return;
215
+ }
216
+ if (message.message.path !== path) {
217
+ return;
218
+ }
219
+ if (message.type === 'typing') {
220
+ clearTimeout(typingMap.current[message.fromParticipantId]);
221
+ typingMap.current[message.fromParticipantId] = setTimeout(() => {
222
+ delete typingMap.current[message.fromParticipantId];
120
223
  setTyping(Object.keys(typingMap.current).length > 0);
121
- }
122
- else if (message.type === "thinking") {
123
- clearTimeout(thinkingMap.current[message.fromParticipantId]);
124
- if (message.message.thinking) {
125
- thinkingMap.current[message.fromParticipantId] = setTimeout(() => {
126
- delete thinkingMap.current[message.fromParticipantId];
127
- setThinking(Object.keys(thinkingMap.current).length > 0);
128
- }, 5000);
129
- }
130
- else {
131
- delete thinkingMap.current[message.fromParticipantId];
132
- }
224
+ }, 1000);
225
+ setTyping(Object.keys(typingMap.current).length > 0);
226
+ return;
227
+ }
228
+ if (message.type !== 'thinking') {
229
+ return;
230
+ }
231
+ clearTimeout(thinkingMap.current[message.fromParticipantId]);
232
+ if (message.message.thinking) {
233
+ thinkingMap.current[message.fromParticipantId] = setTimeout(() => {
234
+ delete thinkingMap.current[message.fromParticipantId];
133
235
  setThinking(Object.keys(thinkingMap.current).length > 0);
134
- }
236
+ }, 5000);
237
+ }
238
+ else {
239
+ delete thinkingMap.current[message.fromParticipantId];
135
240
  }
241
+ setThinking(Object.keys(thinkingMap.current).length > 0);
136
242
  },
137
243
  });
138
- return () => s.unsubscribe();
139
- }, [room, path]);
244
+ return () => {
245
+ subscription.unsubscribe();
246
+ for (const timeout of Object.values(typingMap.current)) {
247
+ clearTimeout(timeout);
248
+ }
249
+ for (const timeout of Object.values(thinkingMap.current)) {
250
+ clearTimeout(timeout);
251
+ }
252
+ typingMap.current = {};
253
+ thinkingMap.current = {};
254
+ };
255
+ }, [path, room]);
140
256
  return { typing, thinking };
141
257
  }
@@ -0,0 +1,2 @@
1
+ import { RoomClient, RemoteParticipant } from '@meshagent/meshagent';
2
+ export declare function useRoomParticipants(room: RoomClient | null): RemoteParticipant[];
@@ -0,0 +1,43 @@
1
+ import { useEffect, useState } from 'react';
2
+ function sameParticipantIds(currentParticipants, nextParticipants) {
3
+ if (currentParticipants.length !== nextParticipants.length) {
4
+ return false;
5
+ }
6
+ const currentParticipantIds = new Set(currentParticipants.map((participant) => participant.id));
7
+ for (const participant of nextParticipants) {
8
+ if (!currentParticipantIds.has(participant.id)) {
9
+ return false;
10
+ }
11
+ }
12
+ return true;
13
+ }
14
+ export function useRoomParticipants(room) {
15
+ const [participants, setParticipants] = useState([]);
16
+ useEffect(() => {
17
+ if (room == null) {
18
+ setParticipants([]);
19
+ return;
20
+ }
21
+ const updateParticipants = () => {
22
+ const nextParticipants = room.messaging.remoteParticipants;
23
+ setParticipants((currentParticipants) => {
24
+ if (sameParticipantIds(currentParticipants, nextParticipants)) {
25
+ return currentParticipants;
26
+ }
27
+ return [...nextParticipants];
28
+ });
29
+ };
30
+ room.messaging.on('participant_added', updateParticipants);
31
+ room.messaging.on('participant_removed', updateParticipants);
32
+ room.messaging.on('messaging_enabled', updateParticipants);
33
+ room.on('disconnected', updateParticipants);
34
+ updateParticipants();
35
+ return () => {
36
+ room.messaging.off('participant_added', updateParticipants);
37
+ room.messaging.off('participant_removed', updateParticipants);
38
+ room.messaging.off('messaging_enabled', updateParticipants);
39
+ room.off('disconnected', updateParticipants);
40
+ };
41
+ }, [room]);
42
+ return participants;
43
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meshagent/meshagent-react",
3
- "version": "0.38.1",
3
+ "version": "0.38.3",
4
4
  "description": "Meshagent React Client",
5
5
  "homepage": "https://github.com/meshagent/meshagent-react",
6
6
  "scripts": {
@@ -36,7 +36,7 @@
36
36
  "base-64": "^1.0.0",
37
37
  "livekit-client": "^2.15.5",
38
38
  "react": "^19.1.0",
39
- "@meshagent/meshagent": "^0.38.1",
39
+ "@meshagent/meshagent": "^0.38.3",
40
40
  "react-dom": "^19.1.0",
41
41
  "typescript": "^5.8.3",
42
42
  "uuid": "^11.1.0",
@@ -1,33 +0,0 @@
1
- import { RoomClient, Element, Participant } from "@meshagent/meshagent";
2
- import { FileUpload } from "./file-upload";
3
- export interface ChatMessageArgs {
4
- id: string;
5
- text: string;
6
- attachments?: string[];
7
- }
8
- export declare class ChatMessage {
9
- id: string;
10
- text: string;
11
- attachments: string[];
12
- constructor({ id, text, attachments }: ChatMessageArgs);
13
- }
14
- export interface UseMessageChatProps {
15
- room: RoomClient;
16
- path: string;
17
- participants?: Participant[];
18
- participantNames?: string[];
19
- includeLocalParticipant?: boolean;
20
- initialMessage?: ChatMessage;
21
- }
22
- export interface UseMessageChatResult {
23
- messages: Element[];
24
- sendMessage: (message: ChatMessage) => void;
25
- selectAttachments: (files: File[]) => void;
26
- attachments: FileUpload[];
27
- setAttachments: (attachments: FileUpload[]) => void;
28
- schemaFileExists: boolean;
29
- onlineParticipants: Participant[];
30
- cancelRequest?: () => void;
31
- }
32
- export declare function fileToAsyncIterable(file: File): AsyncIterable<Uint8Array>;
33
- export declare function useChat({ room, path, participants, participantNames, initialMessage, includeLocalParticipant }: UseMessageChatProps): UseMessageChatResult;
package/dist/cjs/chat.js DELETED
@@ -1,207 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ChatMessage = void 0;
4
- exports.fileToAsyncIterable = fileToAsyncIterable;
5
- exports.useChat = useChat;
6
- const react_1 = require("react");
7
- const meshagent_1 = require("@meshagent/meshagent");
8
- const file_upload_1 = require("./file-upload");
9
- const document_connection_scope_1 = require("./document-connection-scope");
10
- const document_connection_scope_2 = require("./document-connection-scope");
11
- class ChatMessage {
12
- constructor({ id, text, attachments }) {
13
- Object.defineProperty(this, "id", {
14
- enumerable: true,
15
- configurable: true,
16
- writable: true,
17
- value: void 0
18
- });
19
- Object.defineProperty(this, "text", {
20
- enumerable: true,
21
- configurable: true,
22
- writable: true,
23
- value: void 0
24
- });
25
- Object.defineProperty(this, "attachments", {
26
- enumerable: true,
27
- configurable: true,
28
- writable: true,
29
- value: void 0
30
- });
31
- this.id = id;
32
- this.text = text;
33
- this.attachments = attachments ?? [];
34
- }
35
- }
36
- exports.ChatMessage = ChatMessage;
37
- function getParticipantName(participant) {
38
- const name = participant.getAttribute("name");
39
- return typeof name === "string" && name.length > 0 ? name : null;
40
- }
41
- function ensureParticipants(document, localParticipant, includeLocalParticipant, participants, participantNames) {
42
- const retParticipants = [
43
- ...(participants ?? []),
44
- ...(includeLocalParticipant ? [localParticipant] : []),
45
- ];
46
- const existing = new Set();
47
- for (const child of document.root.getChildren()
48
- .filter((c) => c instanceof meshagent_1.Element)) {
49
- if (child.tagName === "members") {
50
- for (const member of child.getChildren()
51
- .filter((c) => c instanceof meshagent_1.Element)) {
52
- const name = getParticipantName(member);
53
- if (name)
54
- existing.add(name);
55
- }
56
- for (const part of retParticipants) {
57
- const name = getParticipantName(part);
58
- if (name && !existing.has(name)) {
59
- child.createChildElement("member", { name });
60
- existing.add(name);
61
- }
62
- }
63
- if (participantNames != null) {
64
- for (const name of participantNames) {
65
- if (!existing.has(name)) {
66
- child.createChildElement("member", { name });
67
- existing.add(name);
68
- }
69
- }
70
- }
71
- }
72
- }
73
- }
74
- function mapMessages(doc) {
75
- const children = doc.root.getChildren() || [];
76
- const thread = children.find((c) => c.tagName === "messages");
77
- const threadChildren = thread?.getChildren() || [];
78
- return threadChildren.filter((el) => el.tagName === "message");
79
- }
80
- function* getParticipantNames(document) {
81
- const children = document.root.getChildren() || [];
82
- const memberNode = children.find((c) => c.tagName === "members");
83
- const members = memberNode?.getChildren() || [];
84
- for (const member of members) {
85
- const name = getParticipantName(member);
86
- if (name) {
87
- yield name;
88
- }
89
- }
90
- }
91
- function* getOnlineParticipants(roomParticipants, participantNames) {
92
- for (const participantName of participantNames) {
93
- for (const remoteParticipant of roomParticipants) {
94
- if (getParticipantName(remoteParticipant) === participantName) {
95
- yield remoteParticipant;
96
- }
97
- }
98
- }
99
- }
100
- const chunkSize = 64 * 1024; // 64 KB
101
- function fileToAsyncIterable(file) {
102
- const hasNativeStream = typeof file.stream === 'function';
103
- async function* nativeStream() {
104
- const reader = file.stream().getReader();
105
- try {
106
- while (true) {
107
- const { done, value } = await reader.read();
108
- if (done)
109
- break;
110
- yield value;
111
- }
112
- }
113
- finally {
114
- reader.releaseLock();
115
- }
116
- }
117
- async function* sliceStream() {
118
- let offset = 0;
119
- while (offset < file.size) {
120
- const blob = file.slice(offset, offset + chunkSize);
121
- const buffer = await blob.arrayBuffer();
122
- yield new Uint8Array(buffer);
123
- offset += chunkSize;
124
- }
125
- }
126
- return (hasNativeStream ? nativeStream : sliceStream)();
127
- }
128
- function useChat({ room, path, participants, participantNames, initialMessage, includeLocalParticipant }) {
129
- const { document, schemaFileExists } = (0, document_connection_scope_2.useDocumentConnection)({
130
- room,
131
- path,
132
- onConnected: (doc) => {
133
- ensureParticipants(doc, room.localParticipant, includeLocalParticipant ?? true, participants ?? [], participantNames ?? []);
134
- if (initialMessage) {
135
- sendMessage(initialMessage);
136
- }
137
- },
138
- onError: (error) => {
139
- console.error("Failed to connect to document:", error);
140
- }
141
- });
142
- const [messages, setMessages] = (0, react_1.useState)(() => document ? mapMessages(document) : []);
143
- const [attachments, setAttachments] = (0, react_1.useState)([]);
144
- const [documentMembers, setDocumentMembers] = (0, react_1.useState)(() => document ? getParticipantNames(document) : []);
145
- (0, document_connection_scope_2.useDocumentChanged)({
146
- document,
147
- onChanged: (doc) => {
148
- setMessages(mapMessages(doc));
149
- setDocumentMembers(getParticipantNames(doc));
150
- },
151
- });
152
- const selectAttachments = (0, react_1.useCallback)((files) => {
153
- const attachmentsToUpload = files.map((file) => new file_upload_1.MeshagentFileUpload(room, `uploaded-files/${file.name}`, fileToAsyncIterable(file), file.size));
154
- setAttachments(attachmentsToUpload);
155
- }, [room]);
156
- const roomParticipants = (0, document_connection_scope_1.useRoomParticipants)(room);
157
- const onlineParticipants = (0, react_1.useMemo)(() => Array.from(getOnlineParticipants(roomParticipants, documentMembers)), [roomParticipants, documentMembers]);
158
- const sendMessage = (0, react_1.useCallback)((message) => {
159
- const children = document?.root.getChildren() || [];
160
- const thread = children.find((c) => c.tagName === "messages");
161
- if (!thread) {
162
- return;
163
- }
164
- const m = thread.createChildElement("message", {
165
- id: message.id,
166
- text: message.text,
167
- created_at: new Date().toISOString(),
168
- author_name: getParticipantName(room.localParticipant) ?? "",
169
- author_ref: null,
170
- });
171
- for (const path of message.attachments) {
172
- m.createChildElement("file", { path });
173
- }
174
- for (const participant of onlineParticipants) {
175
- room.messaging.sendMessage({
176
- to: participant,
177
- type: "chat",
178
- message: {
179
- path,
180
- text: message.text,
181
- attachments: message.attachments.map(path => ({ path })),
182
- },
183
- });
184
- }
185
- }, [document, path, attachments, onlineParticipants, room]);
186
- const cancelRequest = (0, react_1.useCallback)(() => {
187
- for (const participant of onlineParticipants) {
188
- if (participant instanceof meshagent_1.RemoteParticipant && participant.role === 'agent') {
189
- room.messaging.sendMessage({
190
- to: participant,
191
- type: "cancel",
192
- message: { path },
193
- });
194
- }
195
- }
196
- }, [room, path, onlineParticipants]);
197
- return {
198
- messages,
199
- sendMessage,
200
- selectAttachments,
201
- attachments,
202
- setAttachments,
203
- schemaFileExists,
204
- onlineParticipants,
205
- cancelRequest,
206
- };
207
- }
@@ -1,43 +0,0 @@
1
- import { EventEmitter, RoomClient } from "@meshagent/meshagent";
2
- export declare enum UploadStatus {
3
- Initial = "initial",
4
- Uploading = "uploading",
5
- Completed = "completed",
6
- Failed = "failed"
7
- }
8
- interface UploadStatusEvent {
9
- status: UploadStatus;
10
- progress?: number;
11
- }
12
- export declare abstract class FileUpload extends EventEmitter<UploadStatusEvent> {
13
- path: string;
14
- size: number;
15
- protected _status: UploadStatus;
16
- protected constructor(path: string, size?: number);
17
- get status(): UploadStatus;
18
- protected set status(value: UploadStatus);
19
- abstract get bytesUploaded(): number;
20
- abstract get done(): Promise<void>;
21
- get filename(): string;
22
- abstract startUpload(): void;
23
- }
24
- export declare class MeshagentFileUpload extends FileUpload {
25
- readonly room: RoomClient;
26
- readonly dataStream: AsyncIterable<Uint8Array>;
27
- private _bytesUploaded;
28
- private _done;
29
- private _resolveDone;
30
- private _rejectDone;
31
- private _downloadUrl;
32
- private _resolveUrl;
33
- private _rejectUrl;
34
- constructor(room: RoomClient, path: string, dataStream: AsyncIterable<Uint8Array>, size?: number, autoStart?: boolean);
35
- static deferred(room: RoomClient, path: string, dataStream: AsyncIterable<Uint8Array>, size?: number): MeshagentFileUpload;
36
- get bytesUploaded(): number;
37
- get done(): Promise<void>;
38
- /** Resolves to the server’s public download URL – like Dart version. */
39
- get downloadUrl(): Promise<URL>;
40
- startUpload(): void;
41
- private _upload;
42
- }
43
- export {};