Files
apidash/lib/providers/collection_providers.dart
Udhay-Adithya 14ff728870 fix: JS bridge message handling and script execution checks
Refines JS bridge handlers to process messages directly as typed arguments, removing unnecessary JSON decoding and clarifying error logs.
Updates logic to only execute post-response scripts when present and corrects a warning message.
2025-05-16 14:30:58 +05:30

569 lines
18 KiB
Dart

import 'package:apidash/services/flutter_js_service.dart';
import 'package:apidash_core/apidash_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/consts.dart';
import 'providers.dart';
import '../models/models.dart';
import '../services/services.dart' show hiveHandler, HiveHandler;
import '../utils/utils.dart'
show getNewUuid, collectionToHAR, substituteHttpRequestModel;
final selectedIdStateProvider = StateProvider<String?>((ref) => null);
final selectedRequestModelProvider = StateProvider<RequestModel?>((ref) {
final selectedId = ref.watch(selectedIdStateProvider);
final collection = ref.watch(collectionStateNotifierProvider);
if (selectedId == null || collection == null) {
return null;
} else {
return collection[selectedId];
}
});
final requestSequenceProvider = StateProvider<List<String>>((ref) {
var ids = hiveHandler.getIds();
return ids ?? [];
});
final StateNotifierProvider<CollectionStateNotifier, Map<String, RequestModel>?>
collectionStateNotifierProvider =
StateNotifierProvider((ref) => CollectionStateNotifier(
ref,
hiveHandler,
));
class CollectionStateNotifier
extends StateNotifier<Map<String, RequestModel>?> {
CollectionStateNotifier(
this.ref,
this.hiveHandler,
) : super(null) {
var status = loadData();
Future.microtask(() {
if (status) {
ref.read(requestSequenceProvider.notifier).state = [
state!.keys.first,
];
}
ref.read(selectedIdStateProvider.notifier).state =
ref.read(requestSequenceProvider)[0];
});
}
final Ref ref;
final HiveHandler hiveHandler;
final baseHttpResponseModel = const HttpResponseModel();
bool hasId(String id) => state?.keys.contains(id) ?? false;
RequestModel? getRequestModel(String id) {
return state?[id];
}
void unsave() {
ref.read(hasUnsavedChangesProvider.notifier).state = true;
}
void add() {
final id = getNewUuid();
final newRequestModel = RequestModel(
id: id,
httpRequestModel: const HttpRequestModel(),
);
var map = {...state!};
map[id] = newRequestModel;
state = map;
ref
.read(requestSequenceProvider.notifier)
.update((state) => [id, ...state]);
ref.read(selectedIdStateProvider.notifier).state = newRequestModel.id;
unsave();
}
void addRequestModel(
HttpRequestModel httpRequestModel, {
String? name,
}) {
final id = getNewUuid();
final newRequestModel = RequestModel(
id: id,
name: name ?? "",
httpRequestModel: httpRequestModel,
);
var map = {...state!};
map[id] = newRequestModel;
state = map;
ref
.read(requestSequenceProvider.notifier)
.update((state) => [id, ...state]);
ref.read(selectedIdStateProvider.notifier).state = newRequestModel.id;
unsave();
}
void reorder(int oldIdx, int newIdx) {
var itemIds = ref.read(requestSequenceProvider);
final itemId = itemIds.removeAt(oldIdx);
itemIds.insert(newIdx, itemId);
ref.read(requestSequenceProvider.notifier).state = [...itemIds];
unsave();
}
void remove({String? id}) {
final rId = id ?? ref.read(selectedIdStateProvider);
var itemIds = ref.read(requestSequenceProvider);
int idx = itemIds.indexOf(rId!);
cancelHttpRequest(rId);
itemIds.remove(rId);
ref.read(requestSequenceProvider.notifier).state = [...itemIds];
String? newId;
if (idx == 0 && itemIds.isNotEmpty) {
newId = itemIds[0];
} else if (itemIds.length > 1) {
newId = itemIds[idx - 1];
} else {
newId = null;
}
ref.read(selectedIdStateProvider.notifier).state = newId;
var map = {...state!};
map.remove(rId);
state = map;
unsave();
}
void clearResponse({String? id}) {
final rId = id ?? ref.read(selectedIdStateProvider);
if (rId == null || state?[rId] == null) return;
var currentModel = state![rId]!;
final newModel = currentModel.copyWith(
responseStatus: null,
message: null,
httpResponseModel: null,
isWorking: false,
sendingTime: null,
);
var map = {...state!};
map[rId] = newModel;
state = map;
unsave();
}
void duplicate({String? id}) {
final rId = id ?? ref.read(selectedIdStateProvider);
final newId = getNewUuid();
var itemIds = ref.read(requestSequenceProvider);
int idx = itemIds.indexOf(rId!);
var currentModel = state![rId]!;
final newModel = currentModel.copyWith(
id: newId,
name: "${currentModel.name} (copy)",
requestTabIndex: 0,
responseStatus: null,
message: null,
httpResponseModel: null,
isWorking: false,
sendingTime: null,
);
itemIds.insert(idx + 1, newId);
var map = {...state!};
map[newId] = newModel;
state = map;
ref.read(requestSequenceProvider.notifier).state = [...itemIds];
ref.read(selectedIdStateProvider.notifier).state = newId;
unsave();
}
void duplicateFromHistory(HistoryRequestModel historyRequestModel) {
final newId = getNewUuid();
var itemIds = ref.read(requestSequenceProvider);
var currentModel = historyRequestModel;
final newModel = RequestModel(
id: newId,
name: "${currentModel.metaData.name} (history)",
httpRequestModel: currentModel.httpRequestModel,
responseStatus: currentModel.metaData.responseStatus,
message: kResponseCodeReasons[currentModel.metaData.responseStatus],
httpResponseModel: currentModel.httpResponseModel,
isWorking: false,
sendingTime: null,
);
itemIds.insert(0, newId);
var map = {...state!};
map[newId] = newModel;
state = map;
ref.read(requestSequenceProvider.notifier).state = [...itemIds];
ref.read(selectedIdStateProvider.notifier).state = newId;
unsave();
}
void update({
String? id,
HTTPVerb? method,
APIType? apiType,
String? url,
String? name,
String? description,
int? requestTabIndex,
List<NameValueModel>? headers,
List<NameValueModel>? params,
List<bool>? isHeaderEnabledList,
List<bool>? isParamEnabledList,
ContentType? bodyContentType,
String? body,
String? query,
List<FormDataModel>? formData,
int? responseStatus,
String? message,
HttpResponseModel? httpResponseModel,
String? preRequestScript,
String? postRequestScript,
}) {
final rId = id ?? ref.read(selectedIdStateProvider);
if (rId == null) {
debugPrint("Unable to update as Request Id is null");
return;
}
var currentModel = state![rId]!;
var currentHttpRequestModel = currentModel.httpRequestModel;
final newModel = currentModel.copyWith(
apiType: apiType ?? currentModel.apiType,
name: name ?? currentModel.name,
description: description ?? currentModel.description,
requestTabIndex: requestTabIndex ?? currentModel.requestTabIndex,
httpRequestModel: currentHttpRequestModel?.copyWith(
method: method ?? currentHttpRequestModel.method,
url: url ?? currentHttpRequestModel.url,
headers: headers ?? currentHttpRequestModel.headers,
params: params ?? currentHttpRequestModel.params,
isHeaderEnabledList:
isHeaderEnabledList ?? currentHttpRequestModel.isHeaderEnabledList,
isParamEnabledList:
isParamEnabledList ?? currentHttpRequestModel.isParamEnabledList,
bodyContentType:
bodyContentType ?? currentHttpRequestModel.bodyContentType,
body: body ?? currentHttpRequestModel.body,
query: query ?? currentHttpRequestModel.query,
formData: formData ?? currentHttpRequestModel.formData,
),
responseStatus: responseStatus ?? currentModel.responseStatus,
message: message ?? currentModel.message,
httpResponseModel: httpResponseModel ?? currentModel.httpResponseModel,
preRequestScript: preRequestScript ?? currentModel.preRequestScript,
postRequestScript: postRequestScript ?? currentModel.postRequestScript,
);
var map = {...state!};
map[rId] = newModel;
state = map;
unsave();
}
Future<void> handlePreRequestScript(
RequestModel requestModel,
EnvironmentModel? originalEnvironmentModel,
) async {
final scriptResult = await executePreRequestScript(
currentRequestModel: requestModel,
activeEnvironment: originalEnvironmentModel?.toJson() ?? {},
);
requestModel =
requestModel.copyWith(httpRequestModel: scriptResult.updatedRequest);
if (originalEnvironmentModel != null) {
final updatedEnvironmentMap = scriptResult.updatedEnvironment;
final List<EnvironmentVariableModel> newValues = [];
final Map<String, dynamic> mutableUpdatedEnv =
Map.from(updatedEnvironmentMap);
for (final originalVariable in originalEnvironmentModel.values) {
if (mutableUpdatedEnv.containsKey(originalVariable.key)) {
final dynamic newValue = mutableUpdatedEnv[originalVariable.key];
newValues.add(
originalVariable.copyWith(
value: newValue == null ? '' : newValue.toString(),
enabled: true,
),
);
mutableUpdatedEnv.remove(originalVariable.key);
} else {
// Variable was removed by the script (unset/clear), don't add it to newValues.
// Alternatively, you could keep it but set enabled = false:
// newValues.add(originalVariable.copyWith(enabled: false));
}
}
for (final entry in mutableUpdatedEnv.entries) {
final dynamic newValue = entry.value;
newValues.add(
EnvironmentVariableModel(
key: entry.key,
value: newValue == null ? '' : newValue.toString(),
enabled: true,
type: EnvironmentVariableType.variable,
),
);
}
ref.read(environmentsStateNotifierProvider.notifier).updateEnvironment(
originalEnvironmentModel.id,
name: originalEnvironmentModel.name,
values: newValues);
} else {
debugPrint(
"Skipped environment update as originalEnvironmentModel was null.");
if (scriptResult.updatedEnvironment.isNotEmpty) {
debugPrint(
"Warning: Pre-request script updated environment variables, but no active environment was selected to save them to.");
}
}
}
Future<void> handlePostResponseScript(
RequestModel requestModel,
EnvironmentModel? originalEnvironmentModel,
) async {
final scriptResult = await executePostResponseScript(
currentRequestModel: requestModel,
activeEnvironment: originalEnvironmentModel?.toJson() ?? {},
);
requestModel =
requestModel.copyWith(httpResponseModel: scriptResult.updatedResponse);
if (originalEnvironmentModel != null) {
final updatedEnvironmentMap = scriptResult.updatedEnvironment;
final List<EnvironmentVariableModel> newValues = [];
final Map<String, dynamic> mutableUpdatedEnv =
Map.from(updatedEnvironmentMap);
for (final originalVariable in originalEnvironmentModel.values) {
if (mutableUpdatedEnv.containsKey(originalVariable.key)) {
final dynamic newValue = mutableUpdatedEnv[originalVariable.key];
newValues.add(
originalVariable.copyWith(
value: newValue == null ? '' : newValue.toString(),
enabled: true,
),
);
mutableUpdatedEnv.remove(originalVariable.key);
} else {
// Variable was removed by the script (unset/clear), don't add it to newValues.
// Alternatively, you could keep it but set enabled = false:
// newValues.add(originalVariable.copyWith(enabled: false));
}
}
for (final entry in mutableUpdatedEnv.entries) {
final dynamic newValue = entry.value;
newValues.add(
EnvironmentVariableModel(
key: entry.key,
value: newValue == null ? '' : newValue.toString(),
enabled: true,
type: EnvironmentVariableType.variable,
),
);
}
ref.read(environmentsStateNotifierProvider.notifier).updateEnvironment(
originalEnvironmentModel.id,
name: originalEnvironmentModel.name,
values: newValues);
} else {
debugPrint(
"Skipped environment update as originalEnvironmentModel was null.");
if (scriptResult.updatedEnvironment.isNotEmpty) {
debugPrint(
"Warning: Post-response script updated environment variables, but no active environment was selected to save them to.");
}
}
}
Future<void> sendRequest() async {
final requestId = ref.read(selectedIdStateProvider);
ref.read(codePaneVisibleStateProvider.notifier).state = false;
final defaultUriScheme = ref.read(settingsProvider).defaultUriScheme;
final EnvironmentModel? originalEnvironmentModel =
ref.read(selectedEnvironmentModelProvider);
if (requestId == null || state == null) {
return;
}
RequestModel? requestModel = state![requestId];
if (requestModel?.httpRequestModel == null) {
return;
}
if (requestModel != null && requestModel.preRequestScript.isNotEmpty) {
await handlePreRequestScript(requestModel, originalEnvironmentModel);
}
APIType apiType = requestModel!.apiType;
HttpRequestModel substitutedHttpRequestModel =
getSubstitutedHttpRequestModel(requestModel.httpRequestModel!);
// set current model's isWorking to true and update state
var map = {...state!};
map[requestId] = requestModel.copyWith(
isWorking: true,
sendingTime: DateTime.now(),
);
state = map;
bool noSSL = ref.read(settingsProvider).isSSLDisabled;
var responseRec = await sendHttpRequest(
requestId,
apiType,
substitutedHttpRequestModel,
defaultUriScheme: defaultUriScheme,
noSSL: noSSL,
);
late final RequestModel newRequestModel;
if (responseRec.$1 == null) {
newRequestModel = requestModel.copyWith(
responseStatus: -1,
message: responseRec.$3,
isWorking: false,
);
} else {
final httpResponseModel = baseHttpResponseModel.fromResponse(
response: responseRec.$1!,
time: responseRec.$2!,
);
int statusCode = responseRec.$1!.statusCode;
newRequestModel = requestModel.copyWith(
responseStatus: statusCode,
message: kResponseCodeReasons[statusCode],
httpResponseModel: httpResponseModel,
isWorking: false,
);
String newHistoryId = getNewUuid();
HistoryRequestModel model = HistoryRequestModel(
historyId: newHistoryId,
metaData: HistoryMetaModel(
historyId: newHistoryId,
requestId: requestId,
apiType: requestModel.apiType,
name: requestModel.name,
url: substitutedHttpRequestModel.url,
method: substitutedHttpRequestModel.method,
responseStatus: statusCode,
timeStamp: DateTime.now(),
),
httpRequestModel: substitutedHttpRequestModel,
httpResponseModel: httpResponseModel,
);
if (requestModel.postRequestScript.isNotEmpty) {
handlePostResponseScript(newRequestModel, originalEnvironmentModel);
}
ref.read(historyMetaStateNotifier.notifier).addHistoryRequest(model);
}
// update state with response data
map = {...state!};
map[requestId] = newRequestModel;
state = map;
unsave();
}
void cancelRequest() {
final id = ref.read(selectedIdStateProvider);
cancelHttpRequest(id);
unsave();
}
Future<void> clearData() async {
ref.read(clearDataStateProvider.notifier).state = true;
ref.read(selectedIdStateProvider.notifier).state = null;
await hiveHandler.clear();
ref.read(clearDataStateProvider.notifier).state = false;
ref.read(requestSequenceProvider.notifier).state = [];
state = {};
unsave();
}
bool loadData() {
var ids = hiveHandler.getIds();
if (ids == null || ids.length == 0) {
String newId = getNewUuid();
state = {
newId: RequestModel(
id: newId,
httpRequestModel: const HttpRequestModel(),
),
};
return true;
} else {
Map<String, RequestModel> data = {};
for (var id in ids) {
var jsonModel = hiveHandler.getRequestModel(id);
if (jsonModel != null) {
var jsonMap = Map<String, Object?>.from(jsonModel);
var requestModel = RequestModel.fromJson(jsonMap);
if (requestModel.httpRequestModel == null) {
requestModel = requestModel.copyWith(
httpRequestModel: const HttpRequestModel(),
);
}
data[id] = requestModel;
}
}
state = data;
return false;
}
}
Future<void> saveData() async {
ref.read(saveDataStateProvider.notifier).state = true;
final saveResponse = ref.read(settingsProvider).saveResponses;
final ids = ref.read(requestSequenceProvider);
await hiveHandler.setIds(ids);
for (var id in ids) {
await hiveHandler.setRequestModel(
id,
saveResponse
? (state?[id])?.toJson()
: (state?[id]?.copyWith(httpResponseModel: null))?.toJson(),
);
}
await hiveHandler.removeUnused();
ref.read(saveDataStateProvider.notifier).state = false;
ref.read(hasUnsavedChangesProvider.notifier).state = false;
}
Future<Map<String, dynamic>> exportDataToHAR() async {
var result = await collectionToHAR(state?.values.toList());
return result;
// return {
// "data": state!.map((e) => e.toJson(includeResponse: false)).toList()
// };
}
HttpRequestModel getSubstitutedHttpRequestModel(
HttpRequestModel httpRequestModel) {
var envMap = ref.read(availableEnvironmentVariablesStateProvider);
var activeEnvId = ref.read(activeEnvironmentIdStateProvider);
return substituteHttpRequestModel(
httpRequestModel,
envMap,
activeEnvId,
);
}
}