@fluidframework/azure-end-to-end-tests 2.70.0-361248 → 2.70.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/lib/test/multiprocess/childClient.tool.js +226 -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 +151 -100
- package/lib/test/multiprocess/presenceTest.spec.js.map +1 -1
- package/package.json +24 -23
- package/src/test/multiprocess/childClient.tool.ts +277 -131
- package/src/test/multiprocess/messageTypes.ts +36 -0
- package/src/test/multiprocess/orchestratorUtils.ts +121 -23
- package/src/test/multiprocess/presenceTest.spec.ts +202 -125
|
@@ -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,79 @@ 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
|
+
// Special case unexpected telemetry event
|
|
252
|
+
if (event.eventName.endsWith(":JoinResponseWhenAlone")) {
|
|
253
|
+
this.send({
|
|
254
|
+
event: "error",
|
|
255
|
+
error: `Unexpected ClientJoin response. Details: ${JSON.stringify(event.details)}`,
|
|
256
|
+
});
|
|
257
|
+
// Keep going
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const interest = telemetryEventInterestLevel(event.eventName);
|
|
261
|
+
if (interest === "none") {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
this.log.push({
|
|
265
|
+
timestamp: Date.now(),
|
|
266
|
+
agentId: process_id,
|
|
267
|
+
eventCategory: "telemetry",
|
|
268
|
+
eventName: event.eventName,
|
|
269
|
+
details:
|
|
270
|
+
typeof event.details === "string" ? event.details : JSON.stringify(event.details),
|
|
271
|
+
});
|
|
272
|
+
if (verbosity.includes("telem")) {
|
|
273
|
+
selectiveVerboseLog(event, logLevel);
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
private readonly onDisconnected = (): void => {
|
|
279
|
+
// Test state is a bit fragile and does not account for reconnections.
|
|
280
|
+
this.send({ event: "error", error: `${process_id}: Container disconnected` });
|
|
281
|
+
};
|
|
282
|
+
|
|
224
283
|
private registerWorkspace(
|
|
225
284
|
workspaceId: string,
|
|
226
285
|
options: { latest?: boolean; latestMap?: boolean },
|
|
227
286
|
): void {
|
|
228
287
|
if (!this.presence) {
|
|
229
|
-
send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
288
|
+
this.send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
230
289
|
return;
|
|
231
290
|
}
|
|
232
291
|
const { latest, latestMap } = options;
|
|
@@ -244,7 +303,7 @@ class MessageHandler {
|
|
|
244
303
|
// TODO: AB#47518
|
|
245
304
|
const latestState = workspace.states.latest as LatestRaw<{ value: string }>;
|
|
246
305
|
latestState.events.on("remoteUpdated", (update) => {
|
|
247
|
-
send({
|
|
306
|
+
this.send({
|
|
248
307
|
event: "latestValueUpdated",
|
|
249
308
|
workspaceId,
|
|
250
309
|
attendeeId: update.attendee.attendeeId,
|
|
@@ -252,7 +311,7 @@ class MessageHandler {
|
|
|
252
311
|
});
|
|
253
312
|
});
|
|
254
313
|
for (const remote of latestState.getRemotes()) {
|
|
255
|
-
send({
|
|
314
|
+
this.send({
|
|
256
315
|
event: "latestValueUpdated",
|
|
257
316
|
workspaceId,
|
|
258
317
|
attendeeId: remote.attendee.attendeeId,
|
|
@@ -276,7 +335,7 @@ class MessageHandler {
|
|
|
276
335
|
>;
|
|
277
336
|
latestMapState.events.on("remoteUpdated", (update) => {
|
|
278
337
|
for (const [key, valueWithMetadata] of update.items) {
|
|
279
|
-
send({
|
|
338
|
+
this.send({
|
|
280
339
|
event: "latestMapValueUpdated",
|
|
281
340
|
workspaceId,
|
|
282
341
|
attendeeId: update.attendee.attendeeId,
|
|
@@ -287,7 +346,7 @@ class MessageHandler {
|
|
|
287
346
|
});
|
|
288
347
|
for (const remote of latestMapState.getRemotes()) {
|
|
289
348
|
for (const [key, valueWithMetadata] of remote.items) {
|
|
290
|
-
send({
|
|
349
|
+
this.send({
|
|
291
350
|
event: "latestMapValueUpdated",
|
|
292
351
|
workspaceId,
|
|
293
352
|
attendeeId: remote.attendee.attendeeId,
|
|
@@ -299,7 +358,7 @@ class MessageHandler {
|
|
|
299
358
|
}
|
|
300
359
|
|
|
301
360
|
this.workspaces.set(workspaceId, workspace);
|
|
302
|
-
send({
|
|
361
|
+
this.send({
|
|
303
362
|
event: "workspaceRegistered",
|
|
304
363
|
workspaceId,
|
|
305
364
|
latest: latest ?? false,
|
|
@@ -309,15 +368,40 @@ class MessageHandler {
|
|
|
309
368
|
|
|
310
369
|
public async onMessage(msg: MessageFromParent): Promise<void> {
|
|
311
370
|
if (verbosity.includes("msgs")) {
|
|
371
|
+
this.log.push({
|
|
372
|
+
timestamp: Date.now(),
|
|
373
|
+
agentId: process_id,
|
|
374
|
+
eventCategory: "messageReceived",
|
|
375
|
+
eventName: msg.command,
|
|
376
|
+
});
|
|
312
377
|
console.log(`[${process_id}] Received`, msg);
|
|
313
378
|
}
|
|
379
|
+
|
|
380
|
+
if (msg.command === "ping") {
|
|
381
|
+
this.handlePing();
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (msg.command === "connect") {
|
|
386
|
+
await this.handleConnect(msg);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// All other message must wait if connect is in progress
|
|
391
|
+
if (this.msgQueue !== undefined) {
|
|
392
|
+
this.msgQueue.push(msg);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
this.processMessage(msg);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
private processMessage(
|
|
400
|
+
msg: Exclude<MessageFromParent, { command: "ping" | "connect" }>,
|
|
401
|
+
): void {
|
|
314
402
|
switch (msg.command) {
|
|
315
|
-
case "
|
|
316
|
-
this.
|
|
317
|
-
break;
|
|
318
|
-
}
|
|
319
|
-
case "connect": {
|
|
320
|
-
await this.handleConnect(msg);
|
|
403
|
+
case "debugReport": {
|
|
404
|
+
this.handleDebugReport(msg);
|
|
321
405
|
break;
|
|
322
406
|
}
|
|
323
407
|
case "disconnectSelf": {
|
|
@@ -348,68 +432,118 @@ class MessageHandler {
|
|
|
348
432
|
break;
|
|
349
433
|
}
|
|
350
434
|
default: {
|
|
351
|
-
console.error(`${process_id}: Unknown command
|
|
352
|
-
send({
|
|
435
|
+
console.error(`${process_id}: Unknown command:`, msg);
|
|
436
|
+
this.send({
|
|
437
|
+
event: "error",
|
|
438
|
+
error: `${process_id} Unknown command: ${JSON.stringify(msg)}`,
|
|
439
|
+
});
|
|
353
440
|
}
|
|
354
441
|
}
|
|
355
442
|
}
|
|
356
443
|
|
|
357
444
|
private handlePing(): void {
|
|
358
|
-
send({ event: "ack" });
|
|
445
|
+
this.send({ event: "ack" });
|
|
359
446
|
}
|
|
360
447
|
|
|
361
448
|
private async handleConnect(
|
|
362
449
|
msg: Extract<MessageFromParent, { command: "connect" }>,
|
|
363
450
|
): Promise<void> {
|
|
364
451
|
if (!msg.user) {
|
|
365
|
-
send({ event: "error", error: `${process_id}: No azure user information given` });
|
|
452
|
+
this.send({ event: "error", error: `${process_id}: No azure user information given` });
|
|
366
453
|
return;
|
|
367
454
|
}
|
|
368
|
-
if (
|
|
369
|
-
send({ event: "error", error: `${process_id}:
|
|
455
|
+
if (this.container) {
|
|
456
|
+
this.send({ event: "error", error: `${process_id}: Container already loaded` });
|
|
370
457
|
return;
|
|
371
458
|
}
|
|
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
459
|
|
|
389
|
-
//
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
460
|
+
// Prevent reentrance. Queue messages until after connect is fully processed.
|
|
461
|
+
this.msgQueue = [];
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
const { container, containerId, connected } = await getOrCreateContainer({
|
|
465
|
+
...msg,
|
|
466
|
+
logger: this.logger,
|
|
467
|
+
onDisconnected: this.onDisconnected,
|
|
468
|
+
});
|
|
469
|
+
this.container = container;
|
|
470
|
+
const presence = getPresence(container);
|
|
471
|
+
this.presence = presence;
|
|
472
|
+
|
|
473
|
+
// wait for 'ConnectionState.Connected'
|
|
474
|
+
await connected;
|
|
475
|
+
|
|
476
|
+
// Acknowledge connection before sending current attendee information
|
|
477
|
+
this.send({
|
|
478
|
+
event: "connected",
|
|
479
|
+
containerId,
|
|
480
|
+
attendeeId: presence.attendees.getMyself().attendeeId,
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Send existing attendees excluding self to parent/orchestrator
|
|
484
|
+
const self = presence.attendees.getMyself();
|
|
485
|
+
for (const attendee of presence.attendees.getAttendees()) {
|
|
486
|
+
if (attendee !== self && attendee.getConnectionStatus() === "Connected") {
|
|
487
|
+
this.sendAttendeeConnected(attendee);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Listen for presence events to notify parent/orchestrator when a new attendee joins or leaves the session.
|
|
492
|
+
presence.attendees.events.on("attendeeConnected", this.sendAttendeeConnected);
|
|
493
|
+
presence.attendees.events.on("attendeeDisconnected", this.sendAttendeeDisconnected);
|
|
494
|
+
} finally {
|
|
495
|
+
// Process any queued messages received while connecting
|
|
496
|
+
for (const queuedMsg of this.msgQueue) {
|
|
497
|
+
this.processMessage(queuedMsg);
|
|
498
|
+
}
|
|
499
|
+
this.msgQueue = undefined;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
private handleDebugReport(
|
|
504
|
+
msg: Extract<MessageFromParent, { command: "debugReport" }>,
|
|
505
|
+
): void {
|
|
506
|
+
if (msg.reportAttendees) {
|
|
507
|
+
if (this.presence) {
|
|
508
|
+
const attendees = this.presence.attendees.getAttendees();
|
|
509
|
+
let connectedCount = 0;
|
|
510
|
+
for (const attendee of attendees) {
|
|
511
|
+
if (attendee.getConnectionStatus() === "Connected") {
|
|
512
|
+
connectedCount++;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
console.log(
|
|
516
|
+
`[${process_id}] Report: ${attendees.size} attendees, ${connectedCount} connected`,
|
|
517
|
+
);
|
|
518
|
+
} else {
|
|
519
|
+
this.send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
394
520
|
}
|
|
395
521
|
}
|
|
396
522
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
523
|
+
const debugReport: Extract<MessageToParent, { event: "debugReportComplete" }> = {
|
|
524
|
+
event: "debugReportComplete",
|
|
525
|
+
};
|
|
526
|
+
if (msg.sendEventLog) {
|
|
527
|
+
debugReport.log = this.log;
|
|
528
|
+
}
|
|
529
|
+
this.send(debugReport);
|
|
400
530
|
}
|
|
401
531
|
|
|
402
532
|
private handleDisconnectSelf(): void {
|
|
403
533
|
if (!this.container) {
|
|
404
|
-
send({ event: "error", error: `${process_id} is not connected to container` });
|
|
534
|
+
this.send({ event: "error", error: `${process_id} is not connected to container` });
|
|
405
535
|
return;
|
|
406
536
|
}
|
|
537
|
+
// There are no current scenarios where disconnect without presence is expected.
|
|
407
538
|
if (!this.presence) {
|
|
408
|
-
send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
539
|
+
this.send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
409
540
|
return;
|
|
410
541
|
}
|
|
542
|
+
// Disconnect event is treated as an error in normal handling.
|
|
543
|
+
// Remove listener as this disconnect is intentional.
|
|
544
|
+
this.container.off("disconnected", this.onDisconnected);
|
|
411
545
|
this.container.disconnect();
|
|
412
|
-
send({
|
|
546
|
+
this.send({
|
|
413
547
|
event: "disconnectedSelf",
|
|
414
548
|
attendeeId: this.presence.attendees.getMyself().attendeeId,
|
|
415
549
|
});
|
|
@@ -419,19 +553,22 @@ class MessageHandler {
|
|
|
419
553
|
msg: Extract<MessageFromParent, { command: "setLatestValue" }>,
|
|
420
554
|
): void {
|
|
421
555
|
if (!this.presence) {
|
|
422
|
-
send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
556
|
+
this.send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
423
557
|
return;
|
|
424
558
|
}
|
|
425
559
|
const workspace = this.workspaces.get(msg.workspaceId);
|
|
426
560
|
if (!workspace) {
|
|
427
|
-
send({
|
|
561
|
+
this.send({
|
|
562
|
+
event: "error",
|
|
563
|
+
error: `${process_id} workspace ${msg.workspaceId} not found`,
|
|
564
|
+
});
|
|
428
565
|
return;
|
|
429
566
|
}
|
|
430
567
|
// Cast required due to optional keys in WorkspaceSchema
|
|
431
568
|
// TODO: AB#47518
|
|
432
569
|
const latestState = workspace.states.latest as LatestRaw<{ value: string }> | undefined;
|
|
433
570
|
if (!latestState) {
|
|
434
|
-
send({
|
|
571
|
+
this.send({
|
|
435
572
|
event: "error",
|
|
436
573
|
error: `${process_id} latest state not registered for workspace ${msg.workspaceId}`,
|
|
437
574
|
});
|
|
@@ -447,16 +584,19 @@ class MessageHandler {
|
|
|
447
584
|
msg: Extract<MessageFromParent, { command: "setLatestMapValue" }>,
|
|
448
585
|
): void {
|
|
449
586
|
if (!this.presence) {
|
|
450
|
-
send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
587
|
+
this.send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
451
588
|
return;
|
|
452
589
|
}
|
|
453
590
|
if (typeof msg.key !== "string") {
|
|
454
|
-
send({ event: "error", error: `${process_id} invalid key type` });
|
|
591
|
+
this.send({ event: "error", error: `${process_id} invalid key type` });
|
|
455
592
|
return;
|
|
456
593
|
}
|
|
457
594
|
const workspace = this.workspaces.get(msg.workspaceId);
|
|
458
595
|
if (!workspace) {
|
|
459
|
-
send({
|
|
596
|
+
this.send({
|
|
597
|
+
event: "error",
|
|
598
|
+
error: `${process_id} workspace ${msg.workspaceId} not found`,
|
|
599
|
+
});
|
|
460
600
|
return;
|
|
461
601
|
}
|
|
462
602
|
// Cast required due to optional keys in WorkspaceSchema
|
|
@@ -465,7 +605,7 @@ class MessageHandler {
|
|
|
465
605
|
| LatestMapRaw<{ value: Record<string, string | number> }, string>
|
|
466
606
|
| undefined;
|
|
467
607
|
if (!latestMapState) {
|
|
468
|
-
send({
|
|
608
|
+
this.send({
|
|
469
609
|
event: "error",
|
|
470
610
|
error: `${process_id} latestMap state not registered for workspace ${msg.workspaceId}`,
|
|
471
611
|
});
|
|
@@ -481,19 +621,22 @@ class MessageHandler {
|
|
|
481
621
|
msg: Extract<MessageFromParent, { command: "getLatestValue" }>,
|
|
482
622
|
): void {
|
|
483
623
|
if (!this.presence) {
|
|
484
|
-
send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
624
|
+
this.send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
485
625
|
return;
|
|
486
626
|
}
|
|
487
627
|
const workspace = this.workspaces.get(msg.workspaceId);
|
|
488
628
|
if (!workspace) {
|
|
489
|
-
send({
|
|
629
|
+
this.send({
|
|
630
|
+
event: "error",
|
|
631
|
+
error: `${process_id} workspace ${msg.workspaceId} not found`,
|
|
632
|
+
});
|
|
490
633
|
return;
|
|
491
634
|
}
|
|
492
635
|
// Cast required due to optional keys in WorkspaceSchema
|
|
493
636
|
// TODO: AB#47518
|
|
494
637
|
const latestState = workspace.states.latest as LatestRaw<{ value: string }> | undefined;
|
|
495
638
|
if (!latestState) {
|
|
496
|
-
send({
|
|
639
|
+
this.send({
|
|
497
640
|
event: "error",
|
|
498
641
|
error: `${process_id} latest state not registered for workspace ${msg.workspaceId}`,
|
|
499
642
|
});
|
|
@@ -507,7 +650,7 @@ class MessageHandler {
|
|
|
507
650
|
} else {
|
|
508
651
|
value = latestState.local;
|
|
509
652
|
}
|
|
510
|
-
send({
|
|
653
|
+
this.send({
|
|
511
654
|
event: "latestValueGetResponse",
|
|
512
655
|
workspaceId: msg.workspaceId,
|
|
513
656
|
attendeeId: msg.attendeeId,
|
|
@@ -519,16 +662,19 @@ class MessageHandler {
|
|
|
519
662
|
msg: Extract<MessageFromParent, { command: "getLatestMapValue" }>,
|
|
520
663
|
): void {
|
|
521
664
|
if (!this.presence) {
|
|
522
|
-
send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
665
|
+
this.send({ event: "error", error: `${process_id} is not connected to presence` });
|
|
523
666
|
return;
|
|
524
667
|
}
|
|
525
668
|
if (typeof msg.key !== "string") {
|
|
526
|
-
send({ event: "error", error: `${process_id} invalid key type` });
|
|
669
|
+
this.send({ event: "error", error: `${process_id} invalid key type` });
|
|
527
670
|
return;
|
|
528
671
|
}
|
|
529
672
|
const workspace = this.workspaces.get(msg.workspaceId);
|
|
530
673
|
if (!workspace) {
|
|
531
|
-
send({
|
|
674
|
+
this.send({
|
|
675
|
+
event: "error",
|
|
676
|
+
error: `${process_id} workspace ${msg.workspaceId} not found`,
|
|
677
|
+
});
|
|
532
678
|
return;
|
|
533
679
|
}
|
|
534
680
|
// Cast required due to optional keys in WorkspaceSchema
|
|
@@ -537,7 +683,7 @@ class MessageHandler {
|
|
|
537
683
|
| LatestMapRaw<{ value: Record<string, string | number> }, string>
|
|
538
684
|
| undefined;
|
|
539
685
|
if (!latestMapState) {
|
|
540
|
-
send({
|
|
686
|
+
this.send({
|
|
541
687
|
event: "error",
|
|
542
688
|
error: `${process_id} latestMap state not registered for workspace ${msg.workspaceId}`,
|
|
543
689
|
});
|
|
@@ -552,7 +698,7 @@ class MessageHandler {
|
|
|
552
698
|
} else {
|
|
553
699
|
value = latestMapState.local.get(msg.key);
|
|
554
700
|
}
|
|
555
|
-
send({
|
|
701
|
+
this.send({
|
|
556
702
|
event: "latestMapValueGetResponse",
|
|
557
703
|
workspaceId: msg.workspaceId,
|
|
558
704
|
attendeeId: msg.attendeeId,
|