handle method
Implementation
Future<CockpitRemoteSessionEndpointResponse> handle(
CockpitRemoteSessionEndpointRequest request,
) async {
try {
final routePath = _routePathFor(request.uri.path);
switch ((request.method, routePath)) {
case ('GET', '/ping'):
return CockpitRemoteSessionEndpointResponse.json(_pingPayload());
case ('GET', '/ready'):
return CockpitRemoteSessionEndpointResponse.json(
await _readyPayload(),
);
case ('GET', '/health'):
return CockpitRemoteSessionEndpointResponse.json(
(await _statusProvider()).toJson(),
);
case ('GET', '/snapshot'):
final snapshotOptions = _snapshotOptionsFromQuery(
request.uri.queryParameters,
);
final snapshot = await _snapshotProvider(options: snapshotOptions);
final snapshotResponse = await _snapshotResponseFor(
snapshot,
options: snapshotOptions,
);
return CockpitRemoteSessionEndpointResponse.json(
snapshotResponse.artifactDownloads.isEmpty
? snapshotResponse.snapshot.toJson()
: snapshotResponse.toJson(),
);
case ('GET', '/artifacts/download'):
return await _artifactResponseFor(request);
case ('POST', '/commands/execute'):
final command = _decodePayload(
() => CockpitCommand.fromJson(request.jsonBody),
);
await _drainRuntimeSteps(clear: true);
final execution = await _executeCommandWithinBudget(command);
final runtimeSteps = await _drainRuntimeSteps(clear: true);
final artifactRegistration = await _registerCommandArtifacts(
execution,
);
final responsePayload = CockpitRemoteCommandResponse.fromExecution(
CockpitCommandExecution(
result: execution.result,
artifactPayloads: artifactRegistration.inlinePayloads,
runtimeSteps: <CockpitStepRecord>[
...execution.runtimeSteps,
...runtimeSteps,
],
),
).toJson();
if (artifactRegistration.downloads.isNotEmpty) {
responsePayload['artifactDownloads'] = artifactRegistration
.downloads
.map((download) => download.toJson())
.toList(growable: false);
}
return CockpitRemoteSessionEndpointResponse.json(responsePayload);
case ('POST', '/viewport'):
final resizer = _viewportResizer;
if (resizer == null) {
return const CockpitRemoteSessionEndpointResponse.json(
<String, Object?>{
'error': 'viewportUnavailable',
'message': 'Viewport control is unavailable in this session.',
},
statusCode: HttpStatus.notImplemented,
);
}
final viewportRequest = _decodePayload(
() => CockpitViewportResizeRequest.fromJson(request.jsonBody),
);
return CockpitRemoteSessionEndpointResponse.json(
(await resizer(viewportRequest)).toJson(),
);
case ('POST', '/recording/start'):
final recordingRequest = _decodePayload(
() => CockpitRecordingRequest.fromJson(request.jsonBody),
);
final recording = await _handleStartRecording(recordingRequest);
return CockpitRemoteSessionEndpointResponse.json(recording.toJson());
case ('POST', '/recording/stop'):
final result = await _handleStopRecording();
final response = await _recordingResponseFor(result);
return CockpitRemoteSessionEndpointResponse.json(response.toJson());
// Interaction recorder routes. The recorder engine's StateErrors
// (illegal phase, no session) surface through the generic handler
// error envelope (500 serverError), the established convention for
// engine state failures.
case ('POST', '/recorder/start'):
final recorder = _recorderProvider?.call();
if (recorder == null) {
return _recorderUnavailableResponse();
}
final recordingRequest = _decodePayload(
() => CockpitInteractionRecordingRequest.fromJson(request.jsonBody),
);
recorder.start(recordingRequest);
return CockpitRemoteSessionEndpointResponse.json(
recorder.sessionSnapshot().toJson(),
);
case ('POST', '/recorder/pause'):
final recorder = _recorderProvider?.call();
if (recorder == null) {
return _recorderUnavailableResponse();
}
recorder.pause();
return CockpitRemoteSessionEndpointResponse.json(
recorder.sessionSnapshot().toJson(),
);
case ('POST', '/recorder/resume'):
final recorder = _recorderProvider?.call();
if (recorder == null) {
return _recorderUnavailableResponse();
}
recorder.resume();
return CockpitRemoteSessionEndpointResponse.json(
recorder.sessionSnapshot().toJson(),
);
case ('POST', '/recorder/stop'):
final recorder = _recorderProvider?.call();
if (recorder == null) {
return _recorderUnavailableResponse();
}
return CockpitRemoteSessionEndpointResponse.json(
recorder.stop().toJson(),
);
case ('GET', '/recorder/session'):
final recorder = _recorderProvider?.call();
if (recorder == null) {
return _recorderUnavailableResponse();
}
// Before the first start the engine has no session. Answer with a
// minimal idle payload (a 204 would break the JSON-only response
// contract the client enforces), never an error: polling from a
// cold app must succeed.
final session = recorder.activeSession;
if (session == null) {
return const CockpitRemoteSessionEndpointResponse.json(
<String, Object?>{'phase': 'idle'},
);
}
return CockpitRemoteSessionEndpointResponse.json(session.toJson());
case ('GET', '/recorder/steps'):
final recorder = _recorderProvider?.call();
if (recorder == null) {
return _recorderUnavailableResponse();
}
final total = recorder.steps.length;
final after = _recorderStepsCursor(
request.uri.queryParameters,
total,
);
return CockpitRemoteSessionEndpointResponse.json(<String, Object?>{
'steps': <Object?>[
for (final step in recorder.steps.skip(after)) step.toJson(),
],
'total': total,
});
case ('GET', '/recorder/export'):
final recorder = _recorderProvider?.call();
if (recorder == null) {
return _recorderUnavailableResponse();
}
return CockpitRemoteSessionEndpointResponse.json(
recorder.export().toJson(),
);
case ('POST', '/performance/start'):
final starter = _startPerformance;
if (starter == null) {
return const CockpitRemoteSessionEndpointResponse.json(<
String,
Object?
>{
'error': 'performanceUnsupported',
'message': 'Performance capture is unavailable in this session.',
}, statusCode: HttpStatus.notImplemented);
}
final performanceRequest = _decodePayload(
() => CockpitPerformanceCaptureRequest.fromJson(request.jsonBody),
);
final session = await _handleStartPerformance(performanceRequest);
return CockpitRemoteSessionEndpointResponse.json(session.toJson());
case ('POST', '/performance/stop'):
final result = await _handleStopPerformance();
return CockpitRemoteSessionEndpointResponse.json(<String, Object?>{
'report': result.toJson(),
});
default:
return const CockpitRemoteSessionEndpointResponse.json(
<String, Object?>{
'error': 'notFound',
'message': 'Unsupported remote session endpoint.',
},
statusCode: HttpStatus.notFound,
);
}
} on FormatException catch (error) {
return CockpitRemoteSessionEndpointResponse.json(<String, Object?>{
'error': 'invalidPayload',
'message': error.message,
}, statusCode: HttpStatus.badRequest);
} on CockpitInvalidSelector catch (error) {
return CockpitRemoteSessionEndpointResponse.json(<String, Object?>{
'error': 'invalidSelector',
'message': error.message,
if (error.details.isNotEmpty) 'details': error.details,
}, statusCode: HttpStatus.badRequest);
} catch (error) {
return CockpitRemoteSessionEndpointResponse.json(<String, Object?>{
'error': 'serverError',
'message': error.toString(),
}, statusCode: HttpStatus.internalServerError);
}
}