@fluidframework/azure-end-to-end-tests 2.70.0-361092 → 2.70.0-361788
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/lib/test/AzureClientFactory.js +2 -2
- package/lib/test/AzureClientFactory.js.map +1 -1
- package/lib/test/multiprocess/childClient.tool.js +218 -104
- package/lib/test/multiprocess/childClient.tool.js.map +1 -1
- package/lib/test/multiprocess/messageTypes.js.map +1 -1
- package/lib/test/multiprocess/orchestratorUtils.js +72 -17
- package/lib/test/multiprocess/orchestratorUtils.js.map +1 -1
- package/lib/test/multiprocess/presenceTest.spec.js +79 -23
- package/lib/test/multiprocess/presenceTest.spec.js.map +1 -1
- package/lib/test/utils.js +1 -1
- package/lib/test/utils.js.map +1 -1
- package/package.json +23 -23
- package/src/test/AzureClientFactory.ts +2 -2
- package/src/test/multiprocess/childClient.tool.ts +268 -131
- package/src/test/multiprocess/messageTypes.ts +36 -0
- package/src/test/multiprocess/orchestratorUtils.ts +121 -23
- package/src/test/multiprocess/presenceTest.spec.ts +103 -23
- package/src/test/utils.ts +1 -1
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from "@fluidframework/azure-client";
|
|
15
15
|
import { AttachState } from "@fluidframework/container-definitions";
|
|
16
16
|
import { ConnectionState } from "@fluidframework/container-loader";
|
|
17
|
+
import type { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
|
|
17
18
|
import { LogLevel } from "@fluidframework/core-interfaces";
|
|
18
19
|
import type { ScopeType } from "@fluidframework/driver-definitions/legacy";
|
|
19
20
|
import type { ContainerSchema, IFluidContainer } from "@fluidframework/fluid-static";
|
|
@@ -32,10 +33,13 @@ import { timeoutPromise } from "@fluidframework/test-utils/internal";
|
|
|
32
33
|
import { createAzureTokenProvider } from "../AzureTokenFactory.js";
|
|
33
34
|
import { TestDataObject } from "../TestDataObject.js";
|
|
34
35
|
|
|
35
|
-
import type {
|
|
36
|
+
import type {
|
|
37
|
+
MessageFromChild as MessageToParent,
|
|
38
|
+
MessageToChild as MessageFromParent,
|
|
39
|
+
UserIdAndName,
|
|
40
|
+
EventEntry,
|
|
41
|
+
} from "./messageTypes.js";
|
|
36
42
|
|
|
37
|
-
type MessageFromParent = MessageToChild;
|
|
38
|
-
type MessageToParent = Required<MessageFromChild>;
|
|
39
43
|
const connectTimeoutMs = 10_000;
|
|
40
44
|
// Identifier given to child process
|
|
41
45
|
const process_id = process.argv[2];
|
|
@@ -50,41 +54,57 @@ if (useAzure && endPoint === undefined) {
|
|
|
50
54
|
throw new Error("Azure Fluid Relay service endpoint is missing");
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
const containerSchema = {
|
|
58
|
+
initialObjects: {
|
|
59
|
+
// A DataObject is added as otherwise fluid-static complains "Container cannot be initialized without any DataTypes"
|
|
60
|
+
_unused: TestDataObject,
|
|
61
|
+
},
|
|
62
|
+
} as const satisfies ContainerSchema;
|
|
63
|
+
|
|
64
|
+
function telemetryEventInterestLevel(eventName: string): "none" | "basic" | "details" {
|
|
65
|
+
if (eventName.includes(":Signal") || eventName.includes(":Join")) {
|
|
66
|
+
return "details";
|
|
67
|
+
} else if (eventName.includes(":Container:") || eventName.includes(":Presence:")) {
|
|
68
|
+
return "basic";
|
|
69
|
+
}
|
|
70
|
+
return "none";
|
|
71
|
+
}
|
|
72
|
+
|
|
53
73
|
function selectiveVerboseLog(event: ITelemetryBaseEvent, logLevel?: LogLevel): void {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
details: event.details,
|
|
58
|
-
containerConnectionState: event.containerConnectionState,
|
|
59
|
-
});
|
|
60
|
-
} else if (
|
|
61
|
-
event.eventName.includes(":Container:") ||
|
|
62
|
-
event.eventName.includes(":Presence:")
|
|
63
|
-
) {
|
|
64
|
-
console.log(`[${process_id}] [${logLevel ?? LogLevel.default}]`, {
|
|
65
|
-
eventName: event.eventName,
|
|
66
|
-
containerConnectionState: event.containerConnectionState,
|
|
67
|
-
});
|
|
74
|
+
const interest = telemetryEventInterestLevel(event.eventName);
|
|
75
|
+
if (interest === "none") {
|
|
76
|
+
return;
|
|
68
77
|
}
|
|
78
|
+
const content: Record<string, unknown> = {
|
|
79
|
+
eventName: event.eventName,
|
|
80
|
+
containerConnectionState: event.containerConnectionState,
|
|
81
|
+
};
|
|
82
|
+
if (interest === "details") {
|
|
83
|
+
content.details = event.details;
|
|
84
|
+
}
|
|
85
|
+
console.log(`[${process_id}] [${logLevel ?? LogLevel.default}]`, content);
|
|
69
86
|
}
|
|
70
87
|
|
|
71
88
|
/**
|
|
72
|
-
* Get or create a Fluid container
|
|
89
|
+
* Get or create a Fluid container.
|
|
73
90
|
*/
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
91
|
+
const getOrCreateContainer = async (params: {
|
|
92
|
+
logger: ITelemetryBaseLogger;
|
|
93
|
+
onDisconnected: () => void;
|
|
94
|
+
containerId?: string;
|
|
95
|
+
user: UserIdAndName;
|
|
96
|
+
scopes?: ScopeType[];
|
|
97
|
+
createScopes?: ScopeType[];
|
|
98
|
+
}): Promise<{
|
|
99
|
+
container: IFluidContainer<typeof containerSchema>;
|
|
82
100
|
services: AzureContainerServices;
|
|
83
101
|
client: AzureClient;
|
|
84
102
|
containerId: string;
|
|
103
|
+
connected: Promise<void>;
|
|
85
104
|
}> => {
|
|
86
|
-
let container: IFluidContainer
|
|
87
|
-
let containerId
|
|
105
|
+
let container: IFluidContainer<typeof containerSchema>;
|
|
106
|
+
let { containerId } = params;
|
|
107
|
+
const { logger, onDisconnected, user, scopes, createScopes } = params;
|
|
88
108
|
const connectionProps: AzureRemoteConnectionConfig | AzureLocalConnectionConfig = useAzure
|
|
89
109
|
? {
|
|
90
110
|
tenantId,
|
|
@@ -104,46 +124,40 @@ const getOrCreatePresenceContainer = async (
|
|
|
104
124
|
};
|
|
105
125
|
const client = new AzureClient({
|
|
106
126
|
connection: connectionProps,
|
|
107
|
-
logger
|
|
108
|
-
send: verbosity.includes("telem") ? selectiveVerboseLog : () => {},
|
|
109
|
-
},
|
|
127
|
+
logger,
|
|
110
128
|
});
|
|
111
|
-
const schema: ContainerSchema = {
|
|
112
|
-
initialObjects: {
|
|
113
|
-
// A DataObject is added as otherwise fluid-static complains "Container cannot be initialized without any DataTypes"
|
|
114
|
-
_unused: TestDataObject,
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
129
|
let services: AzureContainerServices;
|
|
118
|
-
if (
|
|
119
|
-
({ container, services } = await client.createContainer(
|
|
130
|
+
if (containerId === undefined) {
|
|
131
|
+
({ container, services } = await client.createContainer(containerSchema, "2"));
|
|
120
132
|
containerId = await container.attach();
|
|
121
133
|
} else {
|
|
122
|
-
|
|
123
|
-
({ container, services } = await client.getContainer(containerId, schema, "2"));
|
|
124
|
-
}
|
|
125
|
-
// wait for 'ConnectionState.Connected' so we return with client connected to container
|
|
126
|
-
if (container.connectionState !== ConnectionState.Connected) {
|
|
127
|
-
await timeoutPromise((resolve) => container.once("connected", () => resolve()), {
|
|
128
|
-
durationMs: connectTimeoutMs,
|
|
129
|
-
errorMsg: "container connect() timeout",
|
|
130
|
-
});
|
|
134
|
+
({ container, services } = await client.getContainer(containerId, containerSchema, "2"));
|
|
131
135
|
}
|
|
136
|
+
container.on("disconnected", onDisconnected);
|
|
137
|
+
|
|
138
|
+
const connected =
|
|
139
|
+
container.connectionState === ConnectionState.Connected
|
|
140
|
+
? Promise.resolve()
|
|
141
|
+
: timeoutPromise((resolve) => container.once("connected", () => resolve()), {
|
|
142
|
+
durationMs: connectTimeoutMs,
|
|
143
|
+
errorMsg: "container connect() timeout",
|
|
144
|
+
});
|
|
145
|
+
|
|
132
146
|
assert.strictEqual(
|
|
133
147
|
container.attachState,
|
|
134
148
|
AttachState.Attached,
|
|
135
149
|
"Container is not attached after attach is called",
|
|
136
150
|
);
|
|
137
151
|
|
|
138
|
-
const presence = getPresence(container);
|
|
139
152
|
return {
|
|
140
153
|
client,
|
|
141
154
|
container,
|
|
142
|
-
presence,
|
|
143
155
|
services,
|
|
144
156
|
containerId,
|
|
157
|
+
connected,
|
|
145
158
|
};
|
|
146
159
|
};
|
|
160
|
+
|
|
147
161
|
function createSendFunction(): (msg: MessageToParent) => void {
|
|
148
162
|
if (process.send) {
|
|
149
163
|
const sendFn = process.send.bind(process);
|
|
@@ -160,23 +174,6 @@ function createSendFunction(): (msg: MessageToParent) => void {
|
|
|
160
174
|
|
|
161
175
|
const send = createSendFunction();
|
|
162
176
|
|
|
163
|
-
function sendAttendeeConnected(attendee: Attendee): void {
|
|
164
|
-
send({
|
|
165
|
-
event: "attendeeConnected",
|
|
166
|
-
attendeeId: attendee.attendeeId,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
function sendAttendeeDisconnected(attendee: Attendee): void {
|
|
170
|
-
send({
|
|
171
|
-
event: "attendeeDisconnected",
|
|
172
|
-
attendeeId: attendee.attendeeId,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function isConnected(container: IFluidContainer | undefined): boolean {
|
|
177
|
-
return container !== undefined && container.connectionState === ConnectionState.Connected;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
177
|
function isStringOrNumberRecord(value: unknown): value is Record<string, string | number> {
|
|
181
178
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
182
179
|
return false;
|
|
@@ -216,17 +213,70 @@ type WorkspaceSchema = {
|
|
|
216
213
|
const WorkspaceSchema: WorkspaceSchema = {};
|
|
217
214
|
|
|
218
215
|
class MessageHandler {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
216
|
+
private readonly log: EventEntry[] = [];
|
|
217
|
+
private msgQueue: undefined | Exclude<MessageFromParent, { command: "ping" | "connect" }>[];
|
|
218
|
+
private container: IFluidContainer | undefined;
|
|
219
|
+
private presence: Presence | undefined;
|
|
222
220
|
private readonly workspaces = new Map<string, StatesWorkspace<WorkspaceSchema>>();
|
|
223
221
|
|
|
222
|
+
private send(msg: MessageToParent): void {
|
|
223
|
+
this.log.push({
|
|
224
|
+
timestamp: Date.now(),
|
|
225
|
+
agentId: process_id,
|
|
226
|
+
eventCategory: "messageSent",
|
|
227
|
+
eventName: msg.event,
|
|
228
|
+
details:
|
|
229
|
+
msg.event === "debugReportComplete" && msg.log
|
|
230
|
+
? JSON.stringify({ logLength: msg.log.length })
|
|
231
|
+
: JSON.stringify(msg),
|
|
232
|
+
});
|
|
233
|
+
send(msg);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private readonly sendAttendeeConnected = (attendee: Attendee): void => {
|
|
237
|
+
this.send({
|
|
238
|
+
event: "attendeeConnected",
|
|
239
|
+
attendeeId: attendee.attendeeId,
|
|
240
|
+
});
|
|
241
|
+
};
|
|
242
|
+
private readonly sendAttendeeDisconnected = (attendee: Attendee): void => {
|
|
243
|
+
this.send({
|
|
244
|
+
event: "attendeeDisconnected",
|
|
245
|
+
attendeeId: attendee.attendeeId,
|
|
246
|
+
});
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
private readonly logger: ITelemetryBaseLogger = {
|
|
250
|
+
send: (event: ITelemetryBaseEvent, logLevel?: LogLevel) => {
|
|
251
|
+
const interest = telemetryEventInterestLevel(event.eventName);
|
|
252
|
+
if (interest === "none") {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
this.log.push({
|
|
256
|
+
timestamp: Date.now(),
|
|
257
|
+
agentId: process_id,
|
|
258
|
+
eventCategory: "telemetry",
|
|
259
|
+
eventName: event.eventName,
|
|
260
|
+
details:
|
|
261
|
+
typeof event.details === "string" ? event.details : JSON.stringify(event.details),
|
|
262
|
+
});
|
|
263
|
+
if (verbosity.includes("telem")) {
|
|
264
|
+
selectiveVerboseLog(event, logLevel);
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
private readonly onDisconnected = (): void => {
|
|
270
|
+
// Test state is a bit fragile and does not account for reconnections.
|
|
271
|
+
this.send({ event: "error", error: `${process_id}: Container disconnected` });
|
|
272
|
+
};
|
|
273
|
+
|
|
224
274
|
private registerWorkspace(
|
|
225
275
|
workspaceId: string,
|
|
226
276
|
options: { latest?: boolean; latestMap?: boolean },
|
|
227
277
|
): void {
|
|
228
278
|
if (!this.presence) {
|
|
229
|
-
send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
279
|
+
this.send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
230
280
|
return;
|
|
231
281
|
}
|
|
232
282
|
const { latest, latestMap } = options;
|
|
@@ -244,7 +294,7 @@ class MessageHandler {
|
|
|
244
294
|
// TODO: AB#47518
|
|
245
295
|
const latestState = workspace.states.latest as LatestRaw<{ value: string }>;
|
|
246
296
|
latestState.events.on("remoteUpdated", (update) => {
|
|
247
|
-
send({
|
|
297
|
+
this.send({
|
|
248
298
|
event: "latestValueUpdated",
|
|
249
299
|
workspaceId,
|
|
250
300
|
attendeeId: update.attendee.attendeeId,
|
|
@@ -252,7 +302,7 @@ class MessageHandler {
|
|
|
252
302
|
});
|
|
253
303
|
});
|
|
254
304
|
for (const remote of latestState.getRemotes()) {
|
|
255
|
-
send({
|
|
305
|
+
this.send({
|
|
256
306
|
event: "latestValueUpdated",
|
|
257
307
|
workspaceId,
|
|
258
308
|
attendeeId: remote.attendee.attendeeId,
|
|
@@ -276,7 +326,7 @@ class MessageHandler {
|
|
|
276
326
|
>;
|
|
277
327
|
latestMapState.events.on("remoteUpdated", (update) => {
|
|
278
328
|
for (const [key, valueWithMetadata] of update.items) {
|
|
279
|
-
send({
|
|
329
|
+
this.send({
|
|
280
330
|
event: "latestMapValueUpdated",
|
|
281
331
|
workspaceId,
|
|
282
332
|
attendeeId: update.attendee.attendeeId,
|
|
@@ -287,7 +337,7 @@ class MessageHandler {
|
|
|
287
337
|
});
|
|
288
338
|
for (const remote of latestMapState.getRemotes()) {
|
|
289
339
|
for (const [key, valueWithMetadata] of remote.items) {
|
|
290
|
-
send({
|
|
340
|
+
this.send({
|
|
291
341
|
event: "latestMapValueUpdated",
|
|
292
342
|
workspaceId,
|
|
293
343
|
attendeeId: remote.attendee.attendeeId,
|
|
@@ -299,7 +349,7 @@ class MessageHandler {
|
|
|
299
349
|
}
|
|
300
350
|
|
|
301
351
|
this.workspaces.set(workspaceId, workspace);
|
|
302
|
-
send({
|
|
352
|
+
this.send({
|
|
303
353
|
event: "workspaceRegistered",
|
|
304
354
|
workspaceId,
|
|
305
355
|
latest: latest ?? false,
|
|
@@ -309,15 +359,40 @@ class MessageHandler {
|
|
|
309
359
|
|
|
310
360
|
public async onMessage(msg: MessageFromParent): Promise<void> {
|
|
311
361
|
if (verbosity.includes("msgs")) {
|
|
362
|
+
this.log.push({
|
|
363
|
+
timestamp: Date.now(),
|
|
364
|
+
agentId: process_id,
|
|
365
|
+
eventCategory: "messageReceived",
|
|
366
|
+
eventName: msg.command,
|
|
367
|
+
});
|
|
312
368
|
console.log(`[${process_id}] Received`, msg);
|
|
313
369
|
}
|
|
370
|
+
|
|
371
|
+
if (msg.command === "ping") {
|
|
372
|
+
this.handlePing();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (msg.command === "connect") {
|
|
377
|
+
await this.handleConnect(msg);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// All other message must wait if connect is in progress
|
|
382
|
+
if (this.msgQueue !== undefined) {
|
|
383
|
+
this.msgQueue.push(msg);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
this.processMessage(msg);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private processMessage(
|
|
391
|
+
msg: Exclude<MessageFromParent, { command: "ping" | "connect" }>,
|
|
392
|
+
): void {
|
|
314
393
|
switch (msg.command) {
|
|
315
|
-
case "
|
|
316
|
-
this.
|
|
317
|
-
break;
|
|
318
|
-
}
|
|
319
|
-
case "connect": {
|
|
320
|
-
await this.handleConnect(msg);
|
|
394
|
+
case "debugReport": {
|
|
395
|
+
this.handleDebugReport(msg);
|
|
321
396
|
break;
|
|
322
397
|
}
|
|
323
398
|
case "disconnectSelf": {
|
|
@@ -348,68 +423,118 @@ class MessageHandler {
|
|
|
348
423
|
break;
|
|
349
424
|
}
|
|
350
425
|
default: {
|
|
351
|
-
console.error(`${process_id}: Unknown command
|
|
352
|
-
send({
|
|
426
|
+
console.error(`${process_id}: Unknown command:`, msg);
|
|
427
|
+
this.send({
|
|
428
|
+
event: "error",
|
|
429
|
+
error: `${process_id} Unknown command: ${JSON.stringify(msg)}`,
|
|
430
|
+
});
|
|
353
431
|
}
|
|
354
432
|
}
|
|
355
433
|
}
|
|
356
434
|
|
|
357
435
|
private handlePing(): void {
|
|
358
|
-
send({ event: "ack" });
|
|
436
|
+
this.send({ event: "ack" });
|
|
359
437
|
}
|
|
360
438
|
|
|
361
439
|
private async handleConnect(
|
|
362
440
|
msg: Extract<MessageFromParent, { command: "connect" }>,
|
|
363
441
|
): Promise<void> {
|
|
364
442
|
if (!msg.user) {
|
|
365
|
-
send({ event: "error", error: `${process_id}: No azure user information given` });
|
|
443
|
+
this.send({ event: "error", error: `${process_id}: No azure user information given` });
|
|
366
444
|
return;
|
|
367
445
|
}
|
|
368
|
-
if (
|
|
369
|
-
send({ event: "error", error: `${process_id}:
|
|
446
|
+
if (this.container) {
|
|
447
|
+
this.send({ event: "error", error: `${process_id}: Container already loaded` });
|
|
370
448
|
return;
|
|
371
449
|
}
|
|
372
|
-
const { container, presence, containerId } = await getOrCreatePresenceContainer(
|
|
373
|
-
msg.containerId,
|
|
374
|
-
msg.user,
|
|
375
|
-
msg.scopes,
|
|
376
|
-
msg.createScopes,
|
|
377
|
-
);
|
|
378
|
-
this.container = container;
|
|
379
|
-
this.presence = presence;
|
|
380
|
-
this.containerId = containerId;
|
|
381
|
-
|
|
382
|
-
// Acknowledge connection before sending current attendee information
|
|
383
|
-
send({
|
|
384
|
-
event: "connected",
|
|
385
|
-
containerId,
|
|
386
|
-
attendeeId: presence.attendees.getMyself().attendeeId,
|
|
387
|
-
});
|
|
388
450
|
|
|
389
|
-
//
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
451
|
+
// Prevent reentrance. Queue messages until after connect is fully processed.
|
|
452
|
+
this.msgQueue = [];
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
const { container, containerId, connected } = await getOrCreateContainer({
|
|
456
|
+
...msg,
|
|
457
|
+
logger: this.logger,
|
|
458
|
+
onDisconnected: this.onDisconnected,
|
|
459
|
+
});
|
|
460
|
+
this.container = container;
|
|
461
|
+
const presence = getPresence(container);
|
|
462
|
+
this.presence = presence;
|
|
463
|
+
|
|
464
|
+
// wait for 'ConnectionState.Connected'
|
|
465
|
+
await connected;
|
|
466
|
+
|
|
467
|
+
// Acknowledge connection before sending current attendee information
|
|
468
|
+
this.send({
|
|
469
|
+
event: "connected",
|
|
470
|
+
containerId,
|
|
471
|
+
attendeeId: presence.attendees.getMyself().attendeeId,
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// Send existing attendees excluding self to parent/orchestrator
|
|
475
|
+
const self = presence.attendees.getMyself();
|
|
476
|
+
for (const attendee of presence.attendees.getAttendees()) {
|
|
477
|
+
if (attendee !== self && attendee.getConnectionStatus() === "Connected") {
|
|
478
|
+
this.sendAttendeeConnected(attendee);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Listen for presence events to notify parent/orchestrator when a new attendee joins or leaves the session.
|
|
483
|
+
presence.attendees.events.on("attendeeConnected", this.sendAttendeeConnected);
|
|
484
|
+
presence.attendees.events.on("attendeeDisconnected", this.sendAttendeeDisconnected);
|
|
485
|
+
} finally {
|
|
486
|
+
// Process any queued messages received while connecting
|
|
487
|
+
for (const queuedMsg of this.msgQueue) {
|
|
488
|
+
this.processMessage(queuedMsg);
|
|
489
|
+
}
|
|
490
|
+
this.msgQueue = undefined;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
private handleDebugReport(
|
|
495
|
+
msg: Extract<MessageFromParent, { command: "debugReport" }>,
|
|
496
|
+
): void {
|
|
497
|
+
if (msg.reportAttendees) {
|
|
498
|
+
if (this.presence) {
|
|
499
|
+
const attendees = this.presence.attendees.getAttendees();
|
|
500
|
+
let connectedCount = 0;
|
|
501
|
+
for (const attendee of attendees) {
|
|
502
|
+
if (attendee.getConnectionStatus() === "Connected") {
|
|
503
|
+
connectedCount++;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
console.log(
|
|
507
|
+
`[${process_id}] Report: ${attendees.size} attendees, ${connectedCount} connected`,
|
|
508
|
+
);
|
|
509
|
+
} else {
|
|
510
|
+
this.send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
394
511
|
}
|
|
395
512
|
}
|
|
396
513
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
514
|
+
const debugReport: Extract<MessageToParent, { event: "debugReportComplete" }> = {
|
|
515
|
+
event: "debugReportComplete",
|
|
516
|
+
};
|
|
517
|
+
if (msg.sendEventLog) {
|
|
518
|
+
debugReport.log = this.log;
|
|
519
|
+
}
|
|
520
|
+
this.send(debugReport);
|
|
400
521
|
}
|
|
401
522
|
|
|
402
523
|
private handleDisconnectSelf(): void {
|
|
403
524
|
if (!this.container) {
|
|
404
|
-
send({ event: "error", error: `${process_id} is not connected to container` });
|
|
525
|
+
this.send({ event: "error", error: `${process_id} is not connected to container` });
|
|
405
526
|
return;
|
|
406
527
|
}
|
|
528
|
+
// There are no current scenarios where disconnect without presence is expected.
|
|
407
529
|
if (!this.presence) {
|
|
408
|
-
send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
530
|
+
this.send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
409
531
|
return;
|
|
410
532
|
}
|
|
533
|
+
// Disconnect event is treated as an error in normal handling.
|
|
534
|
+
// Remove listener as this disconnect is intentional.
|
|
535
|
+
this.container.off("disconnected", this.onDisconnected);
|
|
411
536
|
this.container.disconnect();
|
|
412
|
-
send({
|
|
537
|
+
this.send({
|
|
413
538
|
event: "disconnectedSelf",
|
|
414
539
|
attendeeId: this.presence.attendees.getMyself().attendeeId,
|
|
415
540
|
});
|
|
@@ -419,19 +544,22 @@ class MessageHandler {
|
|
|
419
544
|
msg: Extract<MessageFromParent, { command: "setLatestValue" }>,
|
|
420
545
|
): void {
|
|
421
546
|
if (!this.presence) {
|
|
422
|
-
send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
547
|
+
this.send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
423
548
|
return;
|
|
424
549
|
}
|
|
425
550
|
const workspace = this.workspaces.get(msg.workspaceId);
|
|
426
551
|
if (!workspace) {
|
|
427
|
-
send({
|
|
552
|
+
this.send({
|
|
553
|
+
event: "error",
|
|
554
|
+
error: `${process_id} workspace ${msg.workspaceId} not found`,
|
|
555
|
+
});
|
|
428
556
|
return;
|
|
429
557
|
}
|
|
430
558
|
// Cast required due to optional keys in WorkspaceSchema
|
|
431
559
|
// TODO: AB#47518
|
|
432
560
|
const latestState = workspace.states.latest as LatestRaw<{ value: string }> | undefined;
|
|
433
561
|
if (!latestState) {
|
|
434
|
-
send({
|
|
562
|
+
this.send({
|
|
435
563
|
event: "error",
|
|
436
564
|
error: `${process_id} latest state not registered for workspace ${msg.workspaceId}`,
|
|
437
565
|
});
|
|
@@ -447,16 +575,19 @@ class MessageHandler {
|
|
|
447
575
|
msg: Extract<MessageFromParent, { command: "setLatestMapValue" }>,
|
|
448
576
|
): void {
|
|
449
577
|
if (!this.presence) {
|
|
450
|
-
send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
578
|
+
this.send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
451
579
|
return;
|
|
452
580
|
}
|
|
453
581
|
if (typeof msg.key !== "string") {
|
|
454
|
-
send({ event: "error", error: `${process_id} invalid key type` });
|
|
582
|
+
this.send({ event: "error", error: `${process_id} invalid key type` });
|
|
455
583
|
return;
|
|
456
584
|
}
|
|
457
585
|
const workspace = this.workspaces.get(msg.workspaceId);
|
|
458
586
|
if (!workspace) {
|
|
459
|
-
send({
|
|
587
|
+
this.send({
|
|
588
|
+
event: "error",
|
|
589
|
+
error: `${process_id} workspace ${msg.workspaceId} not found`,
|
|
590
|
+
});
|
|
460
591
|
return;
|
|
461
592
|
}
|
|
462
593
|
// Cast required due to optional keys in WorkspaceSchema
|
|
@@ -465,7 +596,7 @@ class MessageHandler {
|
|
|
465
596
|
| LatestMapRaw<{ value: Record<string, string | number> }, string>
|
|
466
597
|
| undefined;
|
|
467
598
|
if (!latestMapState) {
|
|
468
|
-
send({
|
|
599
|
+
this.send({
|
|
469
600
|
event: "error",
|
|
470
601
|
error: `${process_id} latestMap state not registered for workspace ${msg.workspaceId}`,
|
|
471
602
|
});
|
|
@@ -481,19 +612,22 @@ class MessageHandler {
|
|
|
481
612
|
msg: Extract<MessageFromParent, { command: "getLatestValue" }>,
|
|
482
613
|
): void {
|
|
483
614
|
if (!this.presence) {
|
|
484
|
-
send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
615
|
+
this.send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
485
616
|
return;
|
|
486
617
|
}
|
|
487
618
|
const workspace = this.workspaces.get(msg.workspaceId);
|
|
488
619
|
if (!workspace) {
|
|
489
|
-
send({
|
|
620
|
+
this.send({
|
|
621
|
+
event: "error",
|
|
622
|
+
error: `${process_id} workspace ${msg.workspaceId} not found`,
|
|
623
|
+
});
|
|
490
624
|
return;
|
|
491
625
|
}
|
|
492
626
|
// Cast required due to optional keys in WorkspaceSchema
|
|
493
627
|
// TODO: AB#47518
|
|
494
628
|
const latestState = workspace.states.latest as LatestRaw<{ value: string }> | undefined;
|
|
495
629
|
if (!latestState) {
|
|
496
|
-
send({
|
|
630
|
+
this.send({
|
|
497
631
|
event: "error",
|
|
498
632
|
error: `${process_id} latest state not registered for workspace ${msg.workspaceId}`,
|
|
499
633
|
});
|
|
@@ -507,7 +641,7 @@ class MessageHandler {
|
|
|
507
641
|
} else {
|
|
508
642
|
value = latestState.local;
|
|
509
643
|
}
|
|
510
|
-
send({
|
|
644
|
+
this.send({
|
|
511
645
|
event: "latestValueGetResponse",
|
|
512
646
|
workspaceId: msg.workspaceId,
|
|
513
647
|
attendeeId: msg.attendeeId,
|
|
@@ -519,16 +653,19 @@ class MessageHandler {
|
|
|
519
653
|
msg: Extract<MessageFromParent, { command: "getLatestMapValue" }>,
|
|
520
654
|
): void {
|
|
521
655
|
if (!this.presence) {
|
|
522
|
-
send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
656
|
+
this.send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
523
657
|
return;
|
|
524
658
|
}
|
|
525
659
|
if (typeof msg.key !== "string") {
|
|
526
|
-
send({ event: "error", error: `${process_id} invalid key type` });
|
|
660
|
+
this.send({ event: "error", error: `${process_id} invalid key type` });
|
|
527
661
|
return;
|
|
528
662
|
}
|
|
529
663
|
const workspace = this.workspaces.get(msg.workspaceId);
|
|
530
664
|
if (!workspace) {
|
|
531
|
-
send({
|
|
665
|
+
this.send({
|
|
666
|
+
event: "error",
|
|
667
|
+
error: `${process_id} workspace ${msg.workspaceId} not found`,
|
|
668
|
+
});
|
|
532
669
|
return;
|
|
533
670
|
}
|
|
534
671
|
// Cast required due to optional keys in WorkspaceSchema
|
|
@@ -537,7 +674,7 @@ class MessageHandler {
|
|
|
537
674
|
| LatestMapRaw<{ value: Record<string, string | number> }, string>
|
|
538
675
|
| undefined;
|
|
539
676
|
if (!latestMapState) {
|
|
540
|
-
send({
|
|
677
|
+
this.send({
|
|
541
678
|
event: "error",
|
|
542
679
|
error: `${process_id} latestMap state not registered for workspace ${msg.workspaceId}`,
|
|
543
680
|
});
|
|
@@ -552,7 +689,7 @@ class MessageHandler {
|
|
|
552
689
|
} else {
|
|
553
690
|
value = latestMapState.local.get(msg.key);
|
|
554
691
|
}
|
|
555
|
-
send({
|
|
692
|
+
this.send({
|
|
556
693
|
event: "latestMapValueGetResponse",
|
|
557
694
|
workspaceId: msg.workspaceId,
|
|
558
695
|
attendeeId: msg.attendeeId,
|