@fluidframework/azure-end-to-end-tests 2.53.1 → 2.61.0-355054

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 (44) hide show
  1. package/.eslintrc.cjs +27 -0
  2. package/CHANGELOG.md +4 -0
  3. package/lib/test/AzureClientFactory.js +2 -2
  4. package/lib/test/AzureClientFactory.js.map +1 -1
  5. package/lib/test/AzureTokenFactory.js +2 -2
  6. package/lib/test/AzureTokenFactory.js.map +1 -1
  7. package/lib/test/TestDataObject.js +4 -2
  8. package/lib/test/TestDataObject.js.map +1 -1
  9. package/lib/test/audience.spec.js +1 -1
  10. package/lib/test/audience.spec.js.map +1 -1
  11. package/lib/test/containerCreate.spec.js +6 -4
  12. package/lib/test/containerCreate.spec.js.map +1 -1
  13. package/lib/test/ddsTests.spec.js +1 -1
  14. package/lib/test/ddsTests.spec.js.map +1 -1
  15. package/lib/test/multiprocess/childClient.tool.js +454 -0
  16. package/lib/test/multiprocess/childClient.tool.js.map +1 -0
  17. package/lib/test/multiprocess/messageTypes.js.map +1 -1
  18. package/lib/test/multiprocess/orchestratorUtils.js +381 -0
  19. package/lib/test/multiprocess/orchestratorUtils.js.map +1 -0
  20. package/lib/test/multiprocess/presenceTest.spec.js +306 -191
  21. package/lib/test/multiprocess/presenceTest.spec.js.map +1 -1
  22. package/lib/test/tree.spec.js +3 -2
  23. package/lib/test/tree.spec.js.map +1 -1
  24. package/lib/test/utils.js.map +1 -1
  25. package/lib/test/viewContainerVersion.spec.js +1 -1
  26. package/lib/test/viewContainerVersion.spec.js.map +1 -1
  27. package/package.json +29 -29
  28. package/src/test/.mocharc.cjs +0 -1
  29. package/src/test/AzureClientFactory.ts +6 -8
  30. package/src/test/AzureTokenFactory.ts +3 -2
  31. package/src/test/TestDataObject.ts +6 -5
  32. package/src/test/audience.spec.ts +1 -1
  33. package/src/test/containerCreate.spec.ts +6 -5
  34. package/src/test/ddsTests.spec.ts +1 -1
  35. package/src/test/multiprocess/childClient.tool.ts +575 -0
  36. package/src/test/multiprocess/messageTypes.ts +138 -4
  37. package/src/test/multiprocess/orchestratorUtils.ts +573 -0
  38. package/src/test/multiprocess/presenceTest.spec.ts +454 -270
  39. package/src/test/tree.spec.ts +4 -10
  40. package/src/test/utils.ts +1 -1
  41. package/src/test/viewContainerVersion.spec.ts +1 -1
  42. package/lib/test/multiprocess/childClient.js +0 -169
  43. package/lib/test/multiprocess/childClient.js.map +0 -1
  44. package/src/test/multiprocess/childClient.ts +0 -227
@@ -0,0 +1,454 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+ import { strict as assert } from "node:assert";
6
+ import { AzureClient, } from "@fluidframework/azure-client";
7
+ import { AttachState } from "@fluidframework/container-definitions";
8
+ import { ConnectionState } from "@fluidframework/container-loader";
9
+ import { LogLevel } from "@fluidframework/core-interfaces";
10
+ import { getPresence, StateFactory, } from "@fluidframework/presence/beta";
11
+ import { InsecureTokenProvider } from "@fluidframework/test-runtime-utils/internal";
12
+ import { timeoutPromise } from "@fluidframework/test-utils/internal";
13
+ import { createAzureTokenProvider } from "../AzureTokenFactory.js";
14
+ import { TestDataObject } from "../TestDataObject.js";
15
+ const connectTimeoutMs = 10_000;
16
+ // Identifier given to child process
17
+ const process_id = process.argv[2];
18
+ const verbosity = process.argv[3] ?? "";
19
+ const useAzure = process.env.FLUID_CLIENT === "azure";
20
+ const tenantId = useAzure
21
+ ? process.env.azure__fluid__relay__service__tenantId
22
+ : "frs-client-tenant";
23
+ const endPoint = process.env.azure__fluid__relay__service__endpoint;
24
+ if (useAzure && endPoint === undefined) {
25
+ throw new Error("Azure Fluid Relay service endpoint is missing");
26
+ }
27
+ function selectiveVerboseLog(event, logLevel) {
28
+ if (event.eventName.includes(":Signal") || event.eventName.includes(":Join")) {
29
+ console.log(`[${process_id}] [${logLevel ?? LogLevel.default}]`, {
30
+ eventName: event.eventName,
31
+ details: event.details,
32
+ containerConnectionState: event.containerConnectionState,
33
+ });
34
+ }
35
+ else if (event.eventName.includes(":Container:") ||
36
+ event.eventName.includes(":Presence:")) {
37
+ console.log(`[${process_id}] [${logLevel ?? LogLevel.default}]`, {
38
+ eventName: event.eventName,
39
+ containerConnectionState: event.containerConnectionState,
40
+ });
41
+ }
42
+ }
43
+ /**
44
+ * Get or create a Fluid container with Presence in initialObjects.
45
+ */
46
+ const getOrCreatePresenceContainer = async (id, user, scopes, createScopes) => {
47
+ let container;
48
+ let containerId;
49
+ const connectionProps = useAzure
50
+ ? {
51
+ tenantId,
52
+ tokenProvider: createAzureTokenProvider(user.id ?? "foo", user.name ?? "bar", scopes, createScopes),
53
+ endpoint: endPoint,
54
+ type: "remote",
55
+ }
56
+ : {
57
+ tokenProvider: new InsecureTokenProvider("fooBar", user, scopes, createScopes),
58
+ endpoint: "http://localhost:7071",
59
+ type: "local",
60
+ };
61
+ const client = new AzureClient({
62
+ connection: connectionProps,
63
+ logger: {
64
+ send: verbosity.includes("telem") ? selectiveVerboseLog : () => { },
65
+ },
66
+ });
67
+ const schema = {
68
+ initialObjects: {
69
+ // A DataObject is added as otherwise fluid-static complains "Container cannot be initialized without any DataTypes"
70
+ _unused: TestDataObject,
71
+ },
72
+ };
73
+ let services;
74
+ if (id === undefined) {
75
+ ({ container, services } = await client.createContainer(schema, "2"));
76
+ containerId = await container.attach();
77
+ }
78
+ else {
79
+ containerId = id;
80
+ ({ container, services } = await client.getContainer(containerId, schema, "2"));
81
+ }
82
+ // wait for 'ConnectionState.Connected' so we return with client connected to container
83
+ if (container.connectionState !== ConnectionState.Connected) {
84
+ await timeoutPromise((resolve) => container.once("connected", () => resolve()), {
85
+ durationMs: connectTimeoutMs,
86
+ errorMsg: "container connect() timeout",
87
+ });
88
+ }
89
+ assert.strictEqual(container.attachState, AttachState.Attached, "Container is not attached after attach is called");
90
+ const presence = getPresence(container);
91
+ return {
92
+ client,
93
+ container,
94
+ presence,
95
+ services,
96
+ containerId,
97
+ };
98
+ };
99
+ function createSendFunction() {
100
+ if (process.send) {
101
+ const sendFn = process.send.bind(process);
102
+ if (verbosity.includes("msgs")) {
103
+ return (msg) => {
104
+ console.log(`[${process_id}] Sending`, msg);
105
+ sendFn(msg);
106
+ };
107
+ }
108
+ return sendFn;
109
+ }
110
+ throw new Error("process.send is not defined");
111
+ }
112
+ const send = createSendFunction();
113
+ function sendAttendeeConnected(attendee) {
114
+ send({
115
+ event: "attendeeConnected",
116
+ attendeeId: attendee.attendeeId,
117
+ });
118
+ }
119
+ function sendAttendeeDisconnected(attendee) {
120
+ send({
121
+ event: "attendeeDisconnected",
122
+ attendeeId: attendee.attendeeId,
123
+ });
124
+ }
125
+ function isConnected(container) {
126
+ return container !== undefined && container.connectionState === ConnectionState.Connected;
127
+ }
128
+ function isStringOrNumberRecord(value) {
129
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
130
+ return false;
131
+ }
132
+ const stringKeys = Object.keys(value);
133
+ const allKeys = Reflect.ownKeys(value);
134
+ if (stringKeys.length !== allKeys.length) {
135
+ // If there are non-string/symbol keys, return false
136
+ return false;
137
+ }
138
+ for (const key of stringKeys) {
139
+ if (!(typeof value[key] === "string" || typeof value[key] === "number")) {
140
+ return false;
141
+ }
142
+ }
143
+ return true;
144
+ }
145
+ const WorkspaceSchema = {};
146
+ class MessageHandler {
147
+ constructor() {
148
+ this.workspaces = new Map();
149
+ }
150
+ registerWorkspace(workspaceId, options) {
151
+ if (!this.presence) {
152
+ send({ event: "error", error: `${process_id} is not connected to presence` });
153
+ return;
154
+ }
155
+ const { latest, latestMap } = options;
156
+ const workspace = this.presence.states.getWorkspace(`test:${workspaceId}`, WorkspaceSchema);
157
+ if (latest && !workspace.states.latest) {
158
+ workspace.add("latest", StateFactory.latest({ local: { value: "initial" } }));
159
+ // Cast required due to optional keys in WorkspaceSchema
160
+ // TODO: AB#47518
161
+ const latestState = workspace.states.latest;
162
+ latestState.events.on("remoteUpdated", (update) => {
163
+ send({
164
+ event: "latestValueUpdated",
165
+ workspaceId,
166
+ attendeeId: update.attendee.attendeeId,
167
+ value: update.value.value,
168
+ });
169
+ });
170
+ for (const remote of latestState.getRemotes()) {
171
+ send({
172
+ event: "latestValueUpdated",
173
+ workspaceId,
174
+ attendeeId: remote.attendee.attendeeId,
175
+ value: remote.value.value,
176
+ });
177
+ }
178
+ }
179
+ if (latestMap && !workspace.states.latestMap) {
180
+ workspace.add("latestMap", StateFactory.latestMap({
181
+ local: {},
182
+ }));
183
+ // Cast required due to optional keys in WorkspaceSchema
184
+ // TODO: AB#47518
185
+ const latestMapState = workspace.states.latestMap;
186
+ latestMapState.events.on("remoteUpdated", (update) => {
187
+ for (const [key, valueWithMetadata] of update.items) {
188
+ send({
189
+ event: "latestMapValueUpdated",
190
+ workspaceId,
191
+ attendeeId: update.attendee.attendeeId,
192
+ key: String(key),
193
+ value: valueWithMetadata.value.value,
194
+ });
195
+ }
196
+ });
197
+ for (const remote of latestMapState.getRemotes()) {
198
+ for (const [key, valueWithMetadata] of remote.items) {
199
+ send({
200
+ event: "latestMapValueUpdated",
201
+ workspaceId,
202
+ attendeeId: remote.attendee.attendeeId,
203
+ key: String(key),
204
+ value: valueWithMetadata.value.value,
205
+ });
206
+ }
207
+ }
208
+ }
209
+ this.workspaces.set(workspaceId, workspace);
210
+ send({
211
+ event: "workspaceRegistered",
212
+ workspaceId,
213
+ latest: latest ?? false,
214
+ latestMap: latestMap ?? false,
215
+ });
216
+ }
217
+ async onMessage(msg) {
218
+ if (verbosity.includes("msgs")) {
219
+ console.log(`[${process_id}] Received`, msg);
220
+ }
221
+ switch (msg.command) {
222
+ case "ping": {
223
+ this.handlePing();
224
+ break;
225
+ }
226
+ case "connect": {
227
+ await this.handleConnect(msg);
228
+ break;
229
+ }
230
+ case "disconnectSelf": {
231
+ this.handleDisconnectSelf();
232
+ break;
233
+ }
234
+ case "setLatestValue": {
235
+ this.handleSetLatestValue(msg);
236
+ break;
237
+ }
238
+ case "setLatestMapValue": {
239
+ this.handleSetLatestMapValue(msg);
240
+ break;
241
+ }
242
+ case "getLatestValue": {
243
+ this.handleGetLatestValue(msg);
244
+ break;
245
+ }
246
+ case "getLatestMapValue": {
247
+ this.handleGetLatestMapValue(msg);
248
+ break;
249
+ }
250
+ case "registerWorkspace": {
251
+ this.registerWorkspace(msg.workspaceId, {
252
+ latest: msg.latest,
253
+ latestMap: msg.latestMap,
254
+ });
255
+ break;
256
+ }
257
+ default: {
258
+ console.error(`${process_id}: Unknown command`);
259
+ send({ event: "error", error: `${process_id} Unknown command` });
260
+ }
261
+ }
262
+ }
263
+ handlePing() {
264
+ send({ event: "ack" });
265
+ }
266
+ async handleConnect(msg) {
267
+ if (!msg.user) {
268
+ send({ event: "error", error: `${process_id}: No azure user information given` });
269
+ return;
270
+ }
271
+ if (isConnected(this.container)) {
272
+ send({ event: "error", error: `${process_id}: Already connected to container` });
273
+ return;
274
+ }
275
+ const { container, presence, containerId } = await getOrCreatePresenceContainer(msg.containerId, msg.user, msg.scopes, msg.createScopes);
276
+ this.container = container;
277
+ this.presence = presence;
278
+ this.containerId = containerId;
279
+ // Acknowledge connection before sending current attendee information
280
+ send({
281
+ event: "connected",
282
+ containerId,
283
+ attendeeId: presence.attendees.getMyself().attendeeId,
284
+ });
285
+ // Send existing attendees excluding self to parent/orchestrator
286
+ const self = presence.attendees.getMyself();
287
+ for (const attendee of presence.attendees.getAttendees()) {
288
+ if (attendee !== self) {
289
+ sendAttendeeConnected(attendee);
290
+ }
291
+ }
292
+ // Listen for presence events to notify parent/orchestrator when a new attendee joins or leaves the session.
293
+ presence.attendees.events.on("attendeeConnected", sendAttendeeConnected);
294
+ presence.attendees.events.on("attendeeDisconnected", sendAttendeeDisconnected);
295
+ }
296
+ handleDisconnectSelf() {
297
+ if (!this.container) {
298
+ send({ event: "error", error: `${process_id} is not connected to container` });
299
+ return;
300
+ }
301
+ if (!this.presence) {
302
+ send({ event: "error", error: `${process_id} is not connected to presence` });
303
+ return;
304
+ }
305
+ this.container.disconnect();
306
+ send({
307
+ event: "disconnectedSelf",
308
+ attendeeId: this.presence.attendees.getMyself().attendeeId,
309
+ });
310
+ }
311
+ handleSetLatestValue(msg) {
312
+ if (!this.presence) {
313
+ send({ event: "error", error: `${process_id} is not connected to presence` });
314
+ return;
315
+ }
316
+ const workspace = this.workspaces.get(msg.workspaceId);
317
+ if (!workspace) {
318
+ send({ event: "error", error: `${process_id} workspace ${msg.workspaceId} not found` });
319
+ return;
320
+ }
321
+ // Cast required due to optional keys in WorkspaceSchema
322
+ // TODO: AB#47518
323
+ const latestState = workspace.states.latest;
324
+ if (!latestState) {
325
+ send({
326
+ event: "error",
327
+ error: `${process_id} latest state not registered for workspace ${msg.workspaceId}`,
328
+ });
329
+ return;
330
+ }
331
+ if (typeof msg.value !== "string") {
332
+ return;
333
+ }
334
+ latestState.local = { value: msg.value };
335
+ }
336
+ handleSetLatestMapValue(msg) {
337
+ if (!this.presence) {
338
+ send({ event: "error", error: `${process_id} is not connected to presence` });
339
+ return;
340
+ }
341
+ if (typeof msg.key !== "string") {
342
+ send({ event: "error", error: `${process_id} invalid key type` });
343
+ return;
344
+ }
345
+ const workspace = this.workspaces.get(msg.workspaceId);
346
+ if (!workspace) {
347
+ send({ event: "error", error: `${process_id} workspace ${msg.workspaceId} not found` });
348
+ return;
349
+ }
350
+ // Cast required due to optional keys in WorkspaceSchema
351
+ // TODO: AB#47518
352
+ const latestMapState = workspace.states.latestMap;
353
+ if (!latestMapState) {
354
+ send({
355
+ event: "error",
356
+ error: `${process_id} latestMap state not registered for workspace ${msg.workspaceId}`,
357
+ });
358
+ return;
359
+ }
360
+ if (!isStringOrNumberRecord(msg.value)) {
361
+ return;
362
+ }
363
+ latestMapState.local.set(msg.key, { value: msg.value });
364
+ }
365
+ handleGetLatestValue(msg) {
366
+ if (!this.presence) {
367
+ send({ event: "error", error: `${process_id} is not connected to presence` });
368
+ return;
369
+ }
370
+ const workspace = this.workspaces.get(msg.workspaceId);
371
+ if (!workspace) {
372
+ send({ event: "error", error: `${process_id} workspace ${msg.workspaceId} not found` });
373
+ return;
374
+ }
375
+ // Cast required due to optional keys in WorkspaceSchema
376
+ // TODO: AB#47518
377
+ const latestState = workspace.states.latest;
378
+ if (!latestState) {
379
+ send({
380
+ event: "error",
381
+ error: `${process_id} latest state not registered for workspace ${msg.workspaceId}`,
382
+ });
383
+ return;
384
+ }
385
+ let value;
386
+ if (msg.attendeeId) {
387
+ const attendee = this.presence.attendees.getAttendee(msg.attendeeId);
388
+ const remoteData = latestState.getRemote(attendee);
389
+ value = remoteData.value;
390
+ }
391
+ else {
392
+ value = latestState.local;
393
+ }
394
+ send({
395
+ event: "latestValueGetResponse",
396
+ workspaceId: msg.workspaceId,
397
+ attendeeId: msg.attendeeId,
398
+ value: value.value,
399
+ });
400
+ }
401
+ handleGetLatestMapValue(msg) {
402
+ if (!this.presence) {
403
+ send({ event: "error", error: `${process_id} is not connected to presence` });
404
+ return;
405
+ }
406
+ if (typeof msg.key !== "string") {
407
+ send({ event: "error", error: `${process_id} invalid key type` });
408
+ return;
409
+ }
410
+ const workspace = this.workspaces.get(msg.workspaceId);
411
+ if (!workspace) {
412
+ send({ event: "error", error: `${process_id} workspace ${msg.workspaceId} not found` });
413
+ return;
414
+ }
415
+ // Cast required due to optional keys in WorkspaceSchema
416
+ // TODO: AB#47518
417
+ const latestMapState = workspace.states.latestMap;
418
+ if (!latestMapState) {
419
+ send({
420
+ event: "error",
421
+ error: `${process_id} latestMap state not registered for workspace ${msg.workspaceId}`,
422
+ });
423
+ return;
424
+ }
425
+ let value;
426
+ if (msg.attendeeId) {
427
+ const attendee = this.presence.attendees.getAttendee(msg.attendeeId);
428
+ const remoteData = latestMapState.getRemote(attendee);
429
+ const keyData = remoteData.get(msg.key);
430
+ value = keyData?.value;
431
+ }
432
+ else {
433
+ value = latestMapState.local.get(msg.key);
434
+ }
435
+ send({
436
+ event: "latestMapValueGetResponse",
437
+ workspaceId: msg.workspaceId,
438
+ attendeeId: msg.attendeeId,
439
+ key: msg.key,
440
+ value: value?.value,
441
+ });
442
+ }
443
+ }
444
+ function setupMessageHandler() {
445
+ const messageHandler = new MessageHandler();
446
+ process.on("message", (msg) => {
447
+ messageHandler.onMessage(msg).catch((error) => {
448
+ console.error(`Error in client ${process_id}`, error);
449
+ send({ event: "error", error: `${process_id}: ${error.message}` });
450
+ });
451
+ });
452
+ }
453
+ setupMessageHandler();
454
+ //# sourceMappingURL=childClient.tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"childClient.tool.js","sourceRoot":"","sources":["../../../src/test/multiprocess/childClient.tool.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { strict as assert } from \"node:assert\";\n\nimport {\n\tAzureClient,\n\ttype AzureContainerServices,\n\ttype AzureLocalConnectionConfig,\n\ttype AzureRemoteConnectionConfig,\n\ttype ITelemetryBaseEvent,\n} from \"@fluidframework/azure-client\";\nimport { AttachState } from \"@fluidframework/container-definitions\";\nimport { ConnectionState } from \"@fluidframework/container-loader\";\nimport { LogLevel } from \"@fluidframework/core-interfaces\";\nimport type { ScopeType } from \"@fluidframework/driver-definitions/legacy\";\nimport type { ContainerSchema, IFluidContainer } from \"@fluidframework/fluid-static\";\nimport {\n\tgetPresence,\n\ttype Attendee,\n\ttype Presence,\n\tStateFactory,\n\ttype LatestRaw,\n\ttype LatestMapRaw,\n\ttype StatesWorkspace,\n} from \"@fluidframework/presence/beta\";\nimport { InsecureTokenProvider } from \"@fluidframework/test-runtime-utils/internal\";\nimport { timeoutPromise } from \"@fluidframework/test-utils/internal\";\n\nimport { createAzureTokenProvider } from \"../AzureTokenFactory.js\";\nimport { TestDataObject } from \"../TestDataObject.js\";\n\nimport type { MessageFromChild, MessageToChild, UserIdAndName } from \"./messageTypes.js\";\n\ntype MessageFromParent = MessageToChild;\ntype MessageToParent = Required<MessageFromChild>;\nconst connectTimeoutMs = 10_000;\n// Identifier given to child process\nconst process_id = process.argv[2];\nconst verbosity = process.argv[3] ?? \"\";\n\nconst useAzure = process.env.FLUID_CLIENT === \"azure\";\nconst tenantId = useAzure\n\t? (process.env.azure__fluid__relay__service__tenantId as string)\n\t: \"frs-client-tenant\";\nconst endPoint = process.env.azure__fluid__relay__service__endpoint as string;\nif (useAzure && endPoint === undefined) {\n\tthrow new Error(\"Azure Fluid Relay service endpoint is missing\");\n}\n\nfunction selectiveVerboseLog(event: ITelemetryBaseEvent, logLevel?: LogLevel): void {\n\tif (event.eventName.includes(\":Signal\") || event.eventName.includes(\":Join\")) {\n\t\tconsole.log(`[${process_id}] [${logLevel ?? LogLevel.default}]`, {\n\t\t\teventName: event.eventName,\n\t\t\tdetails: event.details,\n\t\t\tcontainerConnectionState: event.containerConnectionState,\n\t\t});\n\t} else if (\n\t\tevent.eventName.includes(\":Container:\") ||\n\t\tevent.eventName.includes(\":Presence:\")\n\t) {\n\t\tconsole.log(`[${process_id}] [${logLevel ?? LogLevel.default}]`, {\n\t\t\teventName: event.eventName,\n\t\t\tcontainerConnectionState: event.containerConnectionState,\n\t\t});\n\t}\n}\n\n/**\n * Get or create a Fluid container with Presence in initialObjects.\n */\nconst getOrCreatePresenceContainer = async (\n\tid: string | undefined,\n\tuser: UserIdAndName,\n\tscopes?: ScopeType[],\n\tcreateScopes?: ScopeType[],\n): Promise<{\n\tcontainer: IFluidContainer;\n\tpresence: Presence;\n\tservices: AzureContainerServices;\n\tclient: AzureClient;\n\tcontainerId: string;\n}> => {\n\tlet container: IFluidContainer;\n\tlet containerId: string;\n\tconst connectionProps: AzureRemoteConnectionConfig | AzureLocalConnectionConfig = useAzure\n\t\t? {\n\t\t\t\ttenantId,\n\t\t\t\ttokenProvider: createAzureTokenProvider(\n\t\t\t\t\tuser.id ?? \"foo\",\n\t\t\t\t\tuser.name ?? \"bar\",\n\t\t\t\t\tscopes,\n\t\t\t\t\tcreateScopes,\n\t\t\t\t),\n\t\t\t\tendpoint: endPoint,\n\t\t\t\ttype: \"remote\",\n\t\t\t}\n\t\t: {\n\t\t\t\ttokenProvider: new InsecureTokenProvider(\"fooBar\", user, scopes, createScopes),\n\t\t\t\tendpoint: \"http://localhost:7071\",\n\t\t\t\ttype: \"local\",\n\t\t\t};\n\tconst client = new AzureClient({\n\t\tconnection: connectionProps,\n\t\tlogger: {\n\t\t\tsend: verbosity.includes(\"telem\") ? selectiveVerboseLog : () => {},\n\t\t},\n\t});\n\tconst schema: ContainerSchema = {\n\t\tinitialObjects: {\n\t\t\t// A DataObject is added as otherwise fluid-static complains \"Container cannot be initialized without any DataTypes\"\n\t\t\t_unused: TestDataObject,\n\t\t},\n\t};\n\tlet services: AzureContainerServices;\n\tif (id === undefined) {\n\t\t({ container, services } = await client.createContainer(schema, \"2\"));\n\t\tcontainerId = await container.attach();\n\t} else {\n\t\tcontainerId = id;\n\t\t({ container, services } = await client.getContainer(containerId, schema, \"2\"));\n\t}\n\t// wait for 'ConnectionState.Connected' so we return with client connected to container\n\tif (container.connectionState !== ConnectionState.Connected) {\n\t\tawait timeoutPromise((resolve) => container.once(\"connected\", () => resolve()), {\n\t\t\tdurationMs: connectTimeoutMs,\n\t\t\terrorMsg: \"container connect() timeout\",\n\t\t});\n\t}\n\tassert.strictEqual(\n\t\tcontainer.attachState,\n\t\tAttachState.Attached,\n\t\t\"Container is not attached after attach is called\",\n\t);\n\n\tconst presence = getPresence(container);\n\treturn {\n\t\tclient,\n\t\tcontainer,\n\t\tpresence,\n\t\tservices,\n\t\tcontainerId,\n\t};\n};\nfunction createSendFunction(): (msg: MessageToParent) => void {\n\tif (process.send) {\n\t\tconst sendFn = process.send.bind(process);\n\t\tif (verbosity.includes(\"msgs\")) {\n\t\t\treturn (msg: MessageToParent) => {\n\t\t\t\tconsole.log(`[${process_id}] Sending`, msg);\n\t\t\t\tsendFn(msg);\n\t\t\t};\n\t\t}\n\t\treturn sendFn;\n\t}\n\tthrow new Error(\"process.send is not defined\");\n}\n\nconst send = createSendFunction();\n\nfunction sendAttendeeConnected(attendee: Attendee): void {\n\tsend({\n\t\tevent: \"attendeeConnected\",\n\t\tattendeeId: attendee.attendeeId,\n\t});\n}\nfunction sendAttendeeDisconnected(attendee: Attendee): void {\n\tsend({\n\t\tevent: \"attendeeDisconnected\",\n\t\tattendeeId: attendee.attendeeId,\n\t});\n}\n\nfunction isConnected(container: IFluidContainer | undefined): boolean {\n\treturn container !== undefined && container.connectionState === ConnectionState.Connected;\n}\n\nfunction isStringOrNumberRecord(value: unknown): value is Record<string, string | number> {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n\t\treturn false;\n\t}\n\n\tconst stringKeys = Object.keys(value);\n\tconst allKeys = Reflect.ownKeys(value);\n\n\tif (stringKeys.length !== allKeys.length) {\n\t\t// If there are non-string/symbol keys, return false\n\t\treturn false;\n\t}\n\tfor (const key of stringKeys) {\n\t\tif (!(typeof value[key] === \"string\" || typeof value[key] === \"number\")) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\n// NOTE:\n// - This schema intentionally uses optional keys (latest?, latestMap?) so tests can register\n// states conditionally at runtime.\n// - Optional keys are not explicitly supported in StatesWorkspace typing today, which means\n// workspace.states.<key> is typed as any. As a result, usages below require casts\n// (e.g., to LatestRaw / LatestMapRaw) to recover concrete types.\n// - Track adding proper optional-key support to Presence state workspace typing here:\n// Work item: AB#47518\n// - Fallout: Until the above is addressed, keep the casts in place and document new usages accordingly.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ntype WorkspaceSchema = {\n\tlatest?: ReturnType<typeof StateFactory.latest<{ value: string }>>;\n\tlatestMap?: ReturnType<\n\t\ttypeof StateFactory.latestMap<{ value: Record<string, string | number> }, string>\n\t>;\n};\nconst WorkspaceSchema: WorkspaceSchema = {};\n\nclass MessageHandler {\n\tpublic presence: Presence | undefined;\n\tpublic container: IFluidContainer | undefined;\n\tpublic containerId: string | undefined;\n\tprivate readonly workspaces = new Map<string, StatesWorkspace<WorkspaceSchema>>();\n\n\tprivate registerWorkspace(\n\t\tworkspaceId: string,\n\t\toptions: { latest?: boolean; latestMap?: boolean },\n\t): void {\n\t\tif (!this.presence) {\n\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to presence` });\n\t\t\treturn;\n\t\t}\n\t\tconst { latest, latestMap } = options;\n\t\tconst workspace: StatesWorkspace<WorkspaceSchema> = this.presence.states.getWorkspace(\n\t\t\t`test:${workspaceId}`,\n\t\t\tWorkspaceSchema,\n\t\t);\n\n\t\tif (latest && !workspace.states.latest) {\n\t\t\tworkspace.add(\n\t\t\t\t\"latest\",\n\t\t\t\tStateFactory.latest<{ value: string }>({ local: { value: \"initial\" } }),\n\t\t\t);\n\t\t\t// Cast required due to optional keys in WorkspaceSchema\n\t\t\t// TODO: AB#47518\n\t\t\tconst latestState = workspace.states.latest as LatestRaw<{ value: string }>;\n\t\t\tlatestState.events.on(\"remoteUpdated\", (update) => {\n\t\t\t\tsend({\n\t\t\t\t\tevent: \"latestValueUpdated\",\n\t\t\t\t\tworkspaceId,\n\t\t\t\t\tattendeeId: update.attendee.attendeeId,\n\t\t\t\t\tvalue: update.value.value,\n\t\t\t\t});\n\t\t\t});\n\t\t\tfor (const remote of latestState.getRemotes()) {\n\t\t\t\tsend({\n\t\t\t\t\tevent: \"latestValueUpdated\",\n\t\t\t\t\tworkspaceId,\n\t\t\t\t\tattendeeId: remote.attendee.attendeeId,\n\t\t\t\t\tvalue: remote.value.value,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (latestMap && !workspace.states.latestMap) {\n\t\t\tworkspace.add(\n\t\t\t\t\"latestMap\",\n\t\t\t\tStateFactory.latestMap<{ value: Record<string, string | number> }, string>({\n\t\t\t\t\tlocal: {},\n\t\t\t\t}),\n\t\t\t);\n\t\t\t// Cast required due to optional keys in WorkspaceSchema\n\t\t\t// TODO: AB#47518\n\t\t\tconst latestMapState = workspace.states.latestMap as LatestMapRaw<\n\t\t\t\t{ value: Record<string, string | number> },\n\t\t\t\tstring\n\t\t\t>;\n\t\t\tlatestMapState.events.on(\"remoteUpdated\", (update) => {\n\t\t\t\tfor (const [key, valueWithMetadata] of update.items) {\n\t\t\t\t\tsend({\n\t\t\t\t\t\tevent: \"latestMapValueUpdated\",\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\tattendeeId: update.attendee.attendeeId,\n\t\t\t\t\t\tkey: String(key),\n\t\t\t\t\t\tvalue: valueWithMetadata.value.value,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\t\t\tfor (const remote of latestMapState.getRemotes()) {\n\t\t\t\tfor (const [key, valueWithMetadata] of remote.items) {\n\t\t\t\t\tsend({\n\t\t\t\t\t\tevent: \"latestMapValueUpdated\",\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t\tattendeeId: remote.attendee.attendeeId,\n\t\t\t\t\t\tkey: String(key),\n\t\t\t\t\t\tvalue: valueWithMetadata.value.value,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.workspaces.set(workspaceId, workspace);\n\t\tsend({\n\t\t\tevent: \"workspaceRegistered\",\n\t\t\tworkspaceId,\n\t\t\tlatest: latest ?? false,\n\t\t\tlatestMap: latestMap ?? false,\n\t\t});\n\t}\n\n\tpublic async onMessage(msg: MessageFromParent): Promise<void> {\n\t\tif (verbosity.includes(\"msgs\")) {\n\t\t\tconsole.log(`[${process_id}] Received`, msg);\n\t\t}\n\t\tswitch (msg.command) {\n\t\t\tcase \"ping\": {\n\t\t\t\tthis.handlePing();\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"connect\": {\n\t\t\t\tawait this.handleConnect(msg);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"disconnectSelf\": {\n\t\t\t\tthis.handleDisconnectSelf();\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"setLatestValue\": {\n\t\t\t\tthis.handleSetLatestValue(msg);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"setLatestMapValue\": {\n\t\t\t\tthis.handleSetLatestMapValue(msg);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"getLatestValue\": {\n\t\t\t\tthis.handleGetLatestValue(msg);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"getLatestMapValue\": {\n\t\t\t\tthis.handleGetLatestMapValue(msg);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"registerWorkspace\": {\n\t\t\t\tthis.registerWorkspace(msg.workspaceId, {\n\t\t\t\t\tlatest: msg.latest,\n\t\t\t\t\tlatestMap: msg.latestMap,\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tconsole.error(`${process_id}: Unknown command`);\n\t\t\t\tsend({ event: \"error\", error: `${process_id} Unknown command` });\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handlePing(): void {\n\t\tsend({ event: \"ack\" });\n\t}\n\n\tprivate async handleConnect(\n\t\tmsg: Extract<MessageFromParent, { command: \"connect\" }>,\n\t): Promise<void> {\n\t\tif (!msg.user) {\n\t\t\tsend({ event: \"error\", error: `${process_id}: No azure user information given` });\n\t\t\treturn;\n\t\t}\n\t\tif (isConnected(this.container)) {\n\t\t\tsend({ event: \"error\", error: `${process_id}: Already connected to container` });\n\t\t\treturn;\n\t\t}\n\t\tconst { container, presence, containerId } = await getOrCreatePresenceContainer(\n\t\t\tmsg.containerId,\n\t\t\tmsg.user,\n\t\t\tmsg.scopes,\n\t\t\tmsg.createScopes,\n\t\t);\n\t\tthis.container = container;\n\t\tthis.presence = presence;\n\t\tthis.containerId = containerId;\n\n\t\t// Acknowledge connection before sending current attendee information\n\t\tsend({\n\t\t\tevent: \"connected\",\n\t\t\tcontainerId,\n\t\t\tattendeeId: presence.attendees.getMyself().attendeeId,\n\t\t});\n\n\t\t// Send existing attendees excluding self to parent/orchestrator\n\t\tconst self = presence.attendees.getMyself();\n\t\tfor (const attendee of presence.attendees.getAttendees()) {\n\t\t\tif (attendee !== self) {\n\t\t\t\tsendAttendeeConnected(attendee);\n\t\t\t}\n\t\t}\n\n\t\t// Listen for presence events to notify parent/orchestrator when a new attendee joins or leaves the session.\n\t\tpresence.attendees.events.on(\"attendeeConnected\", sendAttendeeConnected);\n\t\tpresence.attendees.events.on(\"attendeeDisconnected\", sendAttendeeDisconnected);\n\t}\n\n\tprivate handleDisconnectSelf(): void {\n\t\tif (!this.container) {\n\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to container` });\n\t\t\treturn;\n\t\t}\n\t\tif (!this.presence) {\n\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to presence` });\n\t\t\treturn;\n\t\t}\n\t\tthis.container.disconnect();\n\t\tsend({\n\t\t\tevent: \"disconnectedSelf\",\n\t\t\tattendeeId: this.presence.attendees.getMyself().attendeeId,\n\t\t});\n\t}\n\n\tprivate handleSetLatestValue(\n\t\tmsg: Extract<MessageFromParent, { command: \"setLatestValue\" }>,\n\t): void {\n\t\tif (!this.presence) {\n\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to presence` });\n\t\t\treturn;\n\t\t}\n\t\tconst workspace = this.workspaces.get(msg.workspaceId);\n\t\tif (!workspace) {\n\t\t\tsend({ event: \"error\", error: `${process_id} workspace ${msg.workspaceId} not found` });\n\t\t\treturn;\n\t\t}\n\t\t// Cast required due to optional keys in WorkspaceSchema\n\t\t// TODO: AB#47518\n\t\tconst latestState = workspace.states.latest as LatestRaw<{ value: string }> | undefined;\n\t\tif (!latestState) {\n\t\t\tsend({\n\t\t\t\tevent: \"error\",\n\t\t\t\terror: `${process_id} latest state not registered for workspace ${msg.workspaceId}`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (typeof msg.value !== \"string\") {\n\t\t\treturn;\n\t\t}\n\t\tlatestState.local = { value: msg.value };\n\t}\n\n\tprivate handleSetLatestMapValue(\n\t\tmsg: Extract<MessageFromParent, { command: \"setLatestMapValue\" }>,\n\t): void {\n\t\tif (!this.presence) {\n\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to presence` });\n\t\t\treturn;\n\t\t}\n\t\tif (typeof msg.key !== \"string\") {\n\t\t\tsend({ event: \"error\", error: `${process_id} invalid key type` });\n\t\t\treturn;\n\t\t}\n\t\tconst workspace = this.workspaces.get(msg.workspaceId);\n\t\tif (!workspace) {\n\t\t\tsend({ event: \"error\", error: `${process_id} workspace ${msg.workspaceId} not found` });\n\t\t\treturn;\n\t\t}\n\t\t// Cast required due to optional keys in WorkspaceSchema\n\t\t// TODO: AB#47518\n\t\tconst latestMapState = workspace.states.latestMap as\n\t\t\t| LatestMapRaw<{ value: Record<string, string | number> }, string>\n\t\t\t| undefined;\n\t\tif (!latestMapState) {\n\t\t\tsend({\n\t\t\t\tevent: \"error\",\n\t\t\t\terror: `${process_id} latestMap state not registered for workspace ${msg.workspaceId}`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (!isStringOrNumberRecord(msg.value)) {\n\t\t\treturn;\n\t\t}\n\t\tlatestMapState.local.set(msg.key, { value: msg.value });\n\t}\n\n\tprivate handleGetLatestValue(\n\t\tmsg: Extract<MessageFromParent, { command: \"getLatestValue\" }>,\n\t): void {\n\t\tif (!this.presence) {\n\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to presence` });\n\t\t\treturn;\n\t\t}\n\t\tconst workspace = this.workspaces.get(msg.workspaceId);\n\t\tif (!workspace) {\n\t\t\tsend({ event: \"error\", error: `${process_id} workspace ${msg.workspaceId} not found` });\n\t\t\treturn;\n\t\t}\n\t\t// Cast required due to optional keys in WorkspaceSchema\n\t\t// TODO: AB#47518\n\t\tconst latestState = workspace.states.latest as LatestRaw<{ value: string }> | undefined;\n\t\tif (!latestState) {\n\t\t\tsend({\n\t\t\t\tevent: \"error\",\n\t\t\t\terror: `${process_id} latest state not registered for workspace ${msg.workspaceId}`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tlet value: { value: string };\n\t\tif (msg.attendeeId) {\n\t\t\tconst attendee = this.presence.attendees.getAttendee(msg.attendeeId);\n\t\t\tconst remoteData = latestState.getRemote(attendee);\n\t\t\tvalue = remoteData.value;\n\t\t} else {\n\t\t\tvalue = latestState.local;\n\t\t}\n\t\tsend({\n\t\t\tevent: \"latestValueGetResponse\",\n\t\t\tworkspaceId: msg.workspaceId,\n\t\t\tattendeeId: msg.attendeeId,\n\t\t\tvalue: value.value,\n\t\t});\n\t}\n\n\tprivate handleGetLatestMapValue(\n\t\tmsg: Extract<MessageFromParent, { command: \"getLatestMapValue\" }>,\n\t): void {\n\t\tif (!this.presence) {\n\t\t\tsend({ event: \"error\", error: `${process_id} is not connected to presence` });\n\t\t\treturn;\n\t\t}\n\t\tif (typeof msg.key !== \"string\") {\n\t\t\tsend({ event: \"error\", error: `${process_id} invalid key type` });\n\t\t\treturn;\n\t\t}\n\t\tconst workspace = this.workspaces.get(msg.workspaceId);\n\t\tif (!workspace) {\n\t\t\tsend({ event: \"error\", error: `${process_id} workspace ${msg.workspaceId} not found` });\n\t\t\treturn;\n\t\t}\n\t\t// Cast required due to optional keys in WorkspaceSchema\n\t\t// TODO: AB#47518\n\t\tconst latestMapState = workspace.states.latestMap as\n\t\t\t| LatestMapRaw<{ value: Record<string, string | number> }, string>\n\t\t\t| undefined;\n\t\tif (!latestMapState) {\n\t\t\tsend({\n\t\t\t\tevent: \"error\",\n\t\t\t\terror: `${process_id} latestMap state not registered for workspace ${msg.workspaceId}`,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tlet value: { value: Record<string, string | number> } | undefined;\n\t\tif (msg.attendeeId) {\n\t\t\tconst attendee = this.presence.attendees.getAttendee(msg.attendeeId);\n\t\t\tconst remoteData = latestMapState.getRemote(attendee);\n\t\t\tconst keyData = remoteData.get(msg.key);\n\t\t\tvalue = keyData?.value;\n\t\t} else {\n\t\t\tvalue = latestMapState.local.get(msg.key);\n\t\t}\n\t\tsend({\n\t\t\tevent: \"latestMapValueGetResponse\",\n\t\t\tworkspaceId: msg.workspaceId,\n\t\t\tattendeeId: msg.attendeeId,\n\t\t\tkey: msg.key,\n\t\t\tvalue: value?.value,\n\t\t});\n\t}\n}\n\nfunction setupMessageHandler(): void {\n\tconst messageHandler = new MessageHandler();\n\tprocess.on(\"message\", (msg: MessageFromParent) => {\n\t\tmessageHandler.onMessage(msg).catch((error: Error) => {\n\t\t\tconsole.error(`Error in client ${process_id}`, error);\n\t\t\tsend({ event: \"error\", error: `${process_id}: ${error.message}` });\n\t\t});\n\t});\n}\n\nsetupMessageHandler();\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"messageTypes.js","sourceRoot":"","sources":["../../../src/test/multiprocess/messageTypes.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { AzureUser } from \"@fluidframework/azure-client/internal\";\n// eslint-disable-next-line import/no-internal-modules\nimport type { AttendeeId } from \"@fluidframework/presence/beta\";\n\n/**\n * Message types sent from the orchestrator to the child processes\n */\nexport type MessageToChild = ConnectCommand | DisconnectSelfCommand | PingCommand;\n\n/**\n * Can be sent to check child responsiveness.\n * An {@link AcknowledgeEvent} should be expected in response.\n */\ninterface PingCommand {\n\tcommand: \"ping\";\n}\n\n/**\n * Instructs a child process to connect to a Fluid container.\n * A {@link ConnectedEvent} should be expected in response.\n */\nexport interface ConnectCommand {\n\tcommand: \"connect\";\n\tuser: AzureUser;\n\t/**\n\t * The ID of the Fluid container to connect to.\n\t * If not provided, a new Fluid container will be created.\n\t */\n\tcontainerId?: string;\n}\n\n/**\n * Instructs a child process to disconnect from a Fluid container.\n * A {@link DisconnectedSelfEvent} should be expected in response.\n */\ninterface DisconnectSelfCommand {\n\tcommand: \"disconnectSelf\";\n}\n\n/**\n * Message types sent from the child processes to the orchestrator\n */\nexport type MessageFromChild =\n\t| AcknowledgeEvent\n\t| AttendeeConnectedEvent\n\t| AttendeeDisconnectedEvent\n\t| ConnectedEvent\n\t| DisconnectedSelfEvent\n\t| ErrorEvent;\n\n/**\n * Sent from the child processes to the orchestrator in response to a {@link PingCommand}.\n */\ninterface AcknowledgeEvent {\n\tevent: \"ack\";\n}\n\n/**\n * Sent arbitrarily to indicate a new attendee has connected.\n */\ninterface AttendeeConnectedEvent {\n\tevent: \"attendeeConnected\";\n\tattendeeId: AttendeeId;\n}\n\n/**\n * Sent arbitrarily to indicate an attendee has disconnected.\n */\ninterface AttendeeDisconnectedEvent {\n\tevent: \"attendeeDisconnected\";\n\tattendeeId: AttendeeId;\n}\n\n/**\n * Sent from the child processes to the orchestrator in response to a {@link ConnectCommand}.\n */\ninterface ConnectedEvent {\n\tevent: \"connected\";\n\tcontainerId: string;\n\tattendeeId: AttendeeId;\n}\n\n/**\n * Sent from the child processes to the orchestrator in response to a {@link DisconnectSelfCommand}.\n */\ninterface DisconnectedSelfEvent {\n\tevent: \"disconnectedSelf\";\n\tattendeeId: AttendeeId;\n}\n\n/**\n * Sent at any time to indicate an error.\n */\ninterface ErrorEvent {\n\tevent: \"error\";\n\terror: string;\n}\n"]}
1
+ {"version":3,"file":"messageTypes.js","sourceRoot":"","sources":["../../../src/test/multiprocess/messageTypes.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\n// eslint-disable-next-line import/no-internal-modules\nimport type { JsonSerializable } from \"@fluidframework/core-interfaces/internal\";\nimport type { ScopeType } from \"@fluidframework/driver-definitions/legacy\";\nimport type { AttendeeId } from \"@fluidframework/presence/beta\";\n\nexport interface UserIdAndName {\n\tid: string;\n\tname: string;\n}\n\n/**\n * Message types sent from the orchestrator to the child processes\n */\nexport type MessageToChild =\n\t| ConnectCommand\n\t| DisconnectSelfCommand\n\t| RegisterWorkspaceCommand\n\t| GetLatestValueCommand\n\t| GetLatestMapValueCommand\n\t| SetLatestValueCommand\n\t| SetLatestMapValueCommand\n\t| PingCommand;\n\n/**\n * Can be sent to check child responsiveness.\n * An {@link AcknowledgeEvent} should be expected in response.\n */\ninterface PingCommand {\n\tcommand: \"ping\";\n}\n\n/**\n * Instructs a child process to connect to a Fluid container.\n * A {@link ConnectedEvent} should be expected in response.\n */\nexport interface ConnectCommand {\n\tcommand: \"connect\";\n\tuser: UserIdAndName;\n\tscopes: ScopeType[];\n\tcreateScopes?: ScopeType[];\n\t/**\n\t * The ID of the Fluid container to connect to.\n\t * If not provided, a new Fluid container will be created.\n\t */\n\tcontainerId?: string;\n}\n\n/**\n * Instructs a child process to disconnect from a Fluid container.\n * A {@link DisconnectedSelfEvent} should be expected in response.\n */\ninterface DisconnectSelfCommand {\n\tcommand: \"disconnectSelf\";\n}\n\n/**\n * Instructs a child process to register for state objects in a workspace given a workspaceId\n * A {@link WorkspaceRegisteredEvent} should be expected in response.\n */\ninterface RegisterWorkspaceCommand {\n\tcommand: \"registerWorkspace\";\n\tworkspaceId: string;\n\t/**\n\t * Register a Latest state for this workspace.\n\t */\n\tlatest?: true;\n\t/**\n\t * Register a LatestMap state for this workspace.\n\t */\n\tlatestMap?: true;\n}\n\n/**\n * Instructs a child process to set the latest value.\n * We then can wait for {@link LatestValueUpdatedEvent} from other clients to know when an update occurs that represents this change.\n * Note: The client doesn't guarantee that the update message is directly related to this set command.\n */\ninterface SetLatestValueCommand {\n\tcommand: \"setLatestValue\";\n\tworkspaceId: string;\n\tvalue: JsonSerializable<unknown>;\n}\n\n/**\n * Instructs a child process to set the latest map value.\n * We then can wait for {@link LatestMapValueUpdatedEvent} from other clients to know when an update occurs that represents this change.\n * Note: The client doesn't guarantee that the update message is directly related to this set command.\n */\ninterface SetLatestMapValueCommand {\n\tcommand: \"setLatestMapValue\";\n\tworkspaceId: string;\n\tkey: string;\n\tvalue: JsonSerializable<unknown>;\n}\n\n/**\n * Instructs a child process to get the latest value.\n * A {@link LatestValueGetResponseEvent} should be expected in response.\n */\ninterface GetLatestValueCommand {\n\tcommand: \"getLatestValue\";\n\tworkspaceId: string;\n\tattendeeId?: AttendeeId;\n}\n\n/**\n * Instructs a child process to get the latest map value.\n * A {@link LatestMapValueGetResponseEvent} should be expected in response.\n */\ninterface GetLatestMapValueCommand {\n\tcommand: \"getLatestMapValue\";\n\tworkspaceId: string;\n\tkey: string;\n\tattendeeId?: AttendeeId;\n}\n\n/**\n * Message types sent from the child processes to the orchestrator\n */\nexport type MessageFromChild =\n\t| AcknowledgeEvent\n\t| AttendeeConnectedEvent\n\t| AttendeeDisconnectedEvent\n\t| ConnectedEvent\n\t| DisconnectedSelfEvent\n\t| ErrorEvent\n\t| LatestMapValueGetResponseEvent\n\t| LatestMapValueUpdatedEvent\n\t| LatestValueGetResponseEvent\n\t| LatestValueUpdatedEvent\n\t| WorkspaceRegisteredEvent;\n\n/**\n * Sent from the child processes to the orchestrator in response to a {@link PingCommand}.\n */\ninterface AcknowledgeEvent {\n\tevent: \"ack\";\n}\n\n/**\n * Sent arbitrarily to indicate a new attendee has connected.\n */\ninterface AttendeeConnectedEvent {\n\tevent: \"attendeeConnected\";\n\tattendeeId: AttendeeId;\n}\n\n/**\n * Sent arbitrarily to indicate an attendee has disconnected.\n */\ninterface AttendeeDisconnectedEvent {\n\tevent: \"attendeeDisconnected\";\n\tattendeeId: AttendeeId;\n}\n\n/**\n * Sent from the child processes to the orchestrator in response to a {@link ConnectCommand}.\n */\ninterface ConnectedEvent {\n\tevent: \"connected\";\n\tcontainerId: string;\n\tattendeeId: AttendeeId;\n}\n\n/**\n * Sent from the child processes to the orchestrator in response to a {@link DisconnectSelfCommand}.\n */\ninterface DisconnectedSelfEvent {\n\tevent: \"disconnectedSelf\";\n\tattendeeId: AttendeeId;\n}\n\n/**\n * Sent from the child processes to the orchestrator in response to latest value update.\n */\nexport interface LatestValueUpdatedEvent {\n\tevent: \"latestValueUpdated\";\n\tworkspaceId: string;\n\tattendeeId: AttendeeId;\n\tvalue: unknown;\n}\n\n/**\n * Sent from the child processes to the orchestrator in response to latest map value update.\n */\nexport interface LatestMapValueUpdatedEvent {\n\tevent: \"latestMapValueUpdated\";\n\tworkspaceId: string;\n\tattendeeId: AttendeeId;\n\tkey: string;\n\tvalue: unknown;\n}\n\n/**\n * Sent from the child processes to the orchestrator in response to a {@link GetLatestValueCommand}.\n */\nexport interface LatestValueGetResponseEvent {\n\tevent: \"latestValueGetResponse\";\n\tworkspaceId: string;\n\tattendeeId: AttendeeId | undefined;\n\tvalue: unknown;\n}\n\n/**\n * Sent from the child processes to the orchestrator in response to a {@link GetLatestMapValueCommand}.\n */\nexport interface LatestMapValueGetResponseEvent {\n\tevent: \"latestMapValueGetResponse\";\n\tworkspaceId: string;\n\tattendeeId: AttendeeId | undefined;\n\tkey: string;\n\tvalue: unknown;\n}\n\n/**\n * Sent from the child process to acknowledge workspace registration.\n */\ninterface WorkspaceRegisteredEvent {\n\tevent: \"workspaceRegistered\";\n\tworkspaceId: string;\n\tlatest?: boolean;\n\tlatestMap?: boolean;\n}\n\n/**\n * Sent at any time to indicate an error.\n */\ninterface ErrorEvent {\n\tevent: \"error\";\n\terror: string;\n}\n"]}