Implementation
String encodeDobroDeskOperation(Map<String, Object?> request) {
if (!_json(request)) _invalid();
switch (request['operation']) {
case 'request-client-tool':
_only(request, ['operation', 'input']);
final input = _record(request['input']);
_only(input, ['toolName', 'basisMessageId', 'input']);
if (input['toolName'] is! String ||
!RegExp(r'^[a-z][a-z0-9_]{0,63}$')
.hasMatch(input['toolName'] as String) ||
!_reference(input['basisMessageId'])) _invalid();
_toolPayload(input['input']);
break;
case 'complete-client-tool':
_only(request, ['operation', 'input']);
final input = _record(request['input']);
_only(input, ['callId', 'output']);
if (!_toolCall(input['callId'])) _invalid();
_toolPayload(input['output']);
break;
case 'client-tool-status':
_only(request, ['operation', 'callId']);
if (!_toolCall(request['callId'])) _invalid();
break;
case 'submit-form':
_only(request, ['operation', 'requestId', 'values']);
if (!_reference(request['requestId'])) _invalid();
_record(request['values']);
break;
case 'procedure-action':
_only(request, ['operation', 'input']);
final input = _record(request['input']);
_only(input, [
'action',
'value',
'idempotencyKey',
'procedureId',
'locale',
'runId',
'expectedUpdatedAt',
'stepId',
'parameterId',
'timeZone',
'nativeCommerceUi'
]);
if (![
'start',
'answer',
'availability',
'select',
'calendar_date',
'back',
'confirm',
'decline'
].contains(input['action']) ||
!_text(input['value'], 512) ||
!_key(input['idempotencyKey'])) _invalid();
if (input['action'] == 'start') {
if (!_text(input['procedureId']) ||
!RegExp(r'^[a-z][a-zA-Z0-9_.-]{0,127}$')
.hasMatch(input['procedureId'] as String) ||
input.containsKey('runId') ||
input.containsKey('expectedUpdatedAt')) _invalid();
} else if (!_reference(input['runId']) ||
!(input['runId'] as String).startsWith('wr_') ||
!_date(input['expectedUpdatedAt'])) {
_invalid();
}
if (input.containsKey('locale') &&
!['en', 'uk', 'de', 'es'].contains(input['locale'])) _invalid();
for (final key in ['stepId', 'parameterId']) {
if (input.containsKey(key) &&
(input[key] is! String ||
!RegExp(r'^[a-z][a-z0-9_]{0,63}$')
.hasMatch(input[key] as String))) _invalid();
}
if (input.containsKey('timeZone') && !_text(input['timeZone']))
_invalid();
if (input.containsKey('nativeCommerceUi') &&
input['nativeCommerceUi'] != true) _invalid();
break;
case 'attachment-intents':
_only(request, ['operation', 'messageId', 'attachments']);
if (!_reference(request['messageId']) || request['attachments'] is! List)
_invalid();
final attachments = request['attachments'] as List;
if (attachments.isEmpty || attachments.length > 5) _invalid();
final clientIds = <String>{};
for (final raw in attachments) {
final file = _record(raw);
_only(file, ['clientId', 'fileName', 'mimeType', 'sizeBytes', 'kind']);
if (!_text(file['clientId'], 100) ||
!_text(file['fileName'], 180) ||
RegExp(r'[/\\]').hasMatch(file['fileName'] as String) ||
!_text(file['mimeType']) ||
file['sizeBytes'] is! int ||
(file['sizeBytes'] as int) <= 0 ||
(file['sizeBytes'] as int) > 10 * 1024 * 1024 ||
!['image', 'video', 'pdf', 'text'].contains(file['kind']) ||
!clientIds.add(file['clientId'] as String)) _invalid();
}
break;
case 'upload-attachment':
_only(request, ['operation', 'intent', 'fileUri', 'mimeType']);
final intent = _record(request['intent']);
_only(intent, [
'clientId',
'attachmentId',
'uploadUrl',
'uploadToken',
'expiresAt'
]);
if (!_reference(intent['attachmentId']) ||
!_text(intent['clientId'], 100) ||
!_text(intent['uploadToken'], 8192) ||
!_date(intent['expiresAt']) ||
!_text(request['mimeType']) ||
intent['uploadUrl'] is! String ||
request['fileUri'] is! String) _invalid();
final upload = Uri.tryParse(intent['uploadUrl'] as String);
final file = Uri.tryParse(request['fileUri'] as String);
if (upload == null ||
file == null ||
!(upload.scheme == 'https' ||
upload.scheme == 'http' && upload.host == 'localhost') ||
upload.host.isEmpty ||
upload.userInfo.isNotEmpty ||
upload.hasFragment ||
!upload.path.startsWith('/api/widget/v1/uploads/') ||
!['file', 'content'].contains(file.scheme) ||
file.userInfo.isNotEmpty ||
file.hasQuery ||
file.hasFragment ||
file.scheme == 'file' &&
file.host.isNotEmpty &&
file.host != 'localhost') _invalid();
break;
case 'list-orders':
_only(request, ['operation', 'query']);
if (request.containsKey('query')) {
final query = _record(request['query']);
_only(query, ['cursor', 'store']);
if (query.values.any((value) => !_reference(value))) _invalid();
}
break;
case 'order-detail':
_only(request, ['operation', 'orderRef']);
if (!_reference(request['orderRef'])) _invalid();
break;
case 'create-commerce-proposal':
_only(request, ['operation', 'input', 'idempotencyKey']);
final input = _record(request['input']);
_only(input, ['actionId', 'selection']);
final selection = _record(input['selection']);
_only(selection, ['storeReference', 'orderReference', 'lineItems']);
if (!_key(request['idempotencyKey']) ||
input['actionId'] != 'shopify_order_cancel' ||
!_reference(selection['storeReference']) ||
!_reference(selection['orderReference'])) _invalid();
if (selection.containsKey('lineItems')) {
final items = selection['lineItems'];
if (items is! List || items.length > 100) _invalid();
for (final raw in items) {
final item = _record(raw);
_only(item, ['lineItemReference', 'quantity']);
if (!_reference(item['lineItemReference']) ||
item['quantity'] is! int ||
(item['quantity'] as int) < 1) _invalid();
}
}
break;
case 'confirm-commerce-proposal':
_only(request, ['operation', 'proposalId', 'input', 'idempotencyKey']);
final input = _record(request['input']);
_only(input, ['confirmed']);
if (!_reference(request['proposalId']) ||
!_key(request['idempotencyKey']) ||
input['confirmed'] != true) _invalid();
break;
case 'commerce-proposal':
_only(request, ['operation', 'proposalId']);
if (!_reference(request['proposalId'])) _invalid();
break;
case 'complete-host-action':
_only(request, ['operation', 'requestId', 'actionId', 'result']);
if (request['requestId'] is! String ||
!RegExp(r'^[A-Za-z0-9][A-Za-z0-9:_-]{0,127}$')
.hasMatch(request['requestId'] as String) ||
request['actionId'] is! String ||
!RegExp(r'^[a-z0-9][a-z0-9:_-]{0,63}$')
.hasMatch(request['actionId'] as String)) _invalid();
final result = _record(request['result']);
_only(result, ['ok', 'status', 'data', 'code', 'retryable']);
final status = result['status'];
final ok = result['ok'];
if (ok is! bool ||
status is! String ||
!['acknowledged', 'completed', 'failed'].contains(status) ||
(status == 'failed') == ok) _invalid();
if (result.containsKey('code') &&
(result['code'] is! String ||
!RegExp(r'^[a-z][a-z0-9_]{0,63}$')
.hasMatch(result['code'] as String))) _invalid();
if (result.containsKey('retryable') && result['retryable'] is! bool)
_invalid();
if (utf8.encode(jsonEncode(request)).length > 16 * 1024) _invalid();
break;
default:
_invalid();
}
final encoded = jsonEncode(request);
if (utf8.encode(encoded).length > 65536) _invalid();
return encoded;
}