Reputation: 1
I am using an M1 MacBook Pro, Chrome 122, Flutter 3.3.0 and the HTTP Module to build a company portal web app. I am trying to get some form data to POST to a service now endpoint for incidences. What I believe makes this different from other SO articles is that they are in the context of BackEnd Servers under the Dev's control and people are typically adjusting their servers to make this work. I do not have that option. What I believe my problem is that I'm getting a CORS error because I'm making a request from my web app (Domain A) to Service now (Domain B) and that's Cross Origin.
I have the below code as my poc and it works fine standalone:
import 'package:http/http.dart' as http;
import 'dart:convert';
void createTicket() async {
// // https://stackoverflow.com/questions/50244416/how-to-pass-basic-auth-credentials-in-api-call-for-a-flutter-mobile-application DA GOAT
// SET UP AUTHORIZATION HEADERS FOR THIS REQUEST
String userName = '<REDACTED>';
String password = r'<REDACTED>';
String basicAuth =
'Basic ' + base64.encode(utf8.encode('$userName:$password'));
// // CREATE URI OBJECT FOR OUR API ENDPOINT
Uri apiEndpoint = Uri.parse(
'https://<INSTANCE>.service-now.com/api/x_<NOPE>_<NOPE>_rest/<NOPE>_incident');
// SAMPLE TICKET DATA
Map requestBody = {
"caller_id": "<COMPANYFIELD>",
"assignment_group": "<COMAPNYFIELD>",
"short_description": "Test",
"category": "Software",
"subcategory": "<COMPANYFIELD>",
"priority": "5",
"description": "Test"
};
// USE OUR basicAuth TO POST A TICKET TO THE SERVICE NOW
// API
var response = await http.post(apiEndpoint,
headers: <String, String>{
'authorization': basicAuth,
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
"Access-Control-Allow-Headers": "Access-Control-Allow-Origin, Accept"
},
body: json.encode(requestBody));
print(response.statusCode);
print(response.body);
var breakpoint = 1;
}
void main() {
createTicket();
}
But when I go to put this in my form I get the below error:
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
class TicketingWizardForm extends StatefulWidget {
const TicketingWizardForm({super.key});
@override
State<TicketingWizardForm> createState() => _TicketingWizardFormState();
}
class _TicketingWizardFormState extends State<TicketingWizardForm> {
final _formKey = GlobalKey<FormState>();
// SERVICE NOW: CALLER MACHINERY
// TextEditing Controller for the Employee ID TextFormField. Contains
// what should be the employee's EID so we can set that as the value of
// Requestor, and then use that in our seervice now ticket data map.
final employeeIDController = TextEditingController();
String Requestor = 'not set';
// SERVICE NOW: CATEGORY MACHINERY
String Category = 'not set';
// SERVICE NOW: SUBCATEGORY MACHINERY
List<DropdownMenuEntry> _specificProblemResourceMenuEntries = [];
String Subcategory = 'not set';
// SERVICE NOW: ASSIGNMENT GROUP
String AssignmentGroup = "<COMPANYFIELD>";
// SERVICE NOW: PRIORITY
bool priorityBool = false;
int Priority = 4; // Low Priority unless work stoppage.
// UNIMPLEMENTED VARIABLES
final TextEditingController titleShortdescriptionController =
TextEditingController();
String ShortDescription = "not set";
void fetchLatestTitleShortDescription() {
ShortDescription = titleShortdescriptionController.text;
}
String Description = 'not set';
final TextEditingController descriptionController = TextEditingController();
Future<void> createTicket(
{required String callerId,
required String assignmentGroup,
required String shortDescription,
required String category,
required String subcategory,
required int priority,
required String description}) async {
// // https://stackoverflow.com/questions/50244416/how-to-pass-basic-auth-credentials-in-api-call-for-a-flutter-mobile-application DA GOAT
// SET UP AUTHORIZATION HEADERS FOR THIS REQUEST
String userName = '<REDACTED>';
String password = r'<REDACTED>';
String basicAuth =
'Basic ' + base64.encode(utf8.encode('$userName:$password'));
// // CREATE URI OBJECT FOR OUR API ENDPOINT
Uri apiEndpoint = Uri.https(
'<INSTANCE>.service-now.com','api/x_<NOPE>_<NOPE>_rest/<NOPE>_incident');
// SAMPLE TICKET DATA
Map requestBody = {
"caller_id": callerId,
"assignment_group": "<COMPANYFIELD>",
"short_description": shortDescription,
"category": category,
"subcategory": subcategory,
"priority": priority.toString(),
"description": description
};
// USE OUR basicAuth TO POST A TICKET TO THE SERVICE NOW
// API
var response = await http.post(apiEndpoint,
headers: <String, String>{
'authorization': basicAuth,
'Accept': 'application/json',
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
"Access-Control-Allow-Methods": "POST, GET, OPTIONS, PUT, DELETE, HEAD",
},
body: json.encode(requestBody));
// Show a Bottom Sheet to notify user based on results
if (response.statusCode == 201) {
Scaffold.of(context).showBottomSheet((context) {
print("Successfully Submitted Ticket");
return Container(
child: const Text("Your Ticket was Opened!"),
);
});
} else {
Scaffold.of(context).showBottomSheet((context) {
return Container(
child: const Text("Something went wrong opening your ticket."));
});
}
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// ASSOCIATE ID - TextFormField
TextFormField(
controller: employeeIDController,
onChanged: (text) {
setState(() {
Requestor = employeeIDController.text;
});
},
decoration: const InputDecoration(
label: Text("Employee ID"),
hintText:
"Please enter your associate ID, also called a 'numbered account'"),
),
const SizedBox(height: 12),
// SERVICE NOW: CATEGORY - DropdownMenu, Hardcoded, wont change.
DropdownMenu(
label: const Text("Kind of Issue"),
expandedInsets: const EdgeInsets.all(1),
onSelected: (value) => setState(() {
_specificProblemResourceMenuEntries = [];
Category = value;
if (value == 'Hardware') {
_specificProblemResourceMenuEntries = const [
DropdownMenuEntry(
value: 'Android Tablet', label: 'Android Tablet'),
DropdownMenuEntry(
value: 'Android Smartphone',
label: 'Android Phone'),
DropdownMenuEntry(
value: 'Computer', label: 'Computer'),
DropdownMenuEntry(
value: 'Computer Accessory',
label: 'Computer Accessory'),
DropdownMenuEntry(value: 'Display', label: 'Display'),
DropdownMenuEntry(value: 'Fax', label: 'Fax Machine'),
DropdownMenuEntry(value: 'iPad/iPod', label: 'iPad'),
DropdownMenuEntry(value: 'iPhone', label: 'iPhone'),
DropdownMenuEntry(
value: 'Time Clock', label: 'Time Clock'),
DropdownMenuEntry(
value: 'Windows Tablet', label: 'Windows Tablet'),
DropdownMenuEntry(
value: 'Zebra Handheld', label: 'Zebra Handheld')
];
}
if (value == 'Software') {
_specificProblemResourceMenuEntries = const [
DropdownMenuEntry(
value: 'CarMax Application',
label: 'CarMax Application'),
DropdownMenuEntry(
value: 'Microsoft Office',
label: 'Microsoft Office'),
DropdownMenuEntry(
value: 'Photo Station',
label: 'Photo Station',
)
];
}
if (value == 'Network') {
_specificProblemResourceMenuEntries = const [
DropdownMenuEntry(
value: "T-Comm Voice", label: "T-Comm Voice"),
DropdownMenuEntry(
value: "T-Comm Data", label: "T-Comm Data"),
];
}
}),
hintText: "This is about...",
dropdownMenuEntries: const <DropdownMenuEntry>[
DropdownMenuEntry(
value: "Hardware",
label:
"Hardware - Computers, Peripherals, Phones, Time Clocks, etc."),
DropdownMenuEntry(
value: "Software",
label:
"Software - Office, Supported Web Applications, License Requests etc."),
DropdownMenuEntry(
value: "Network",
label:
"Network - Speed, Perfomance, SSIDs, Connectivity failures etc.")
]),
const SizedBox(height: 12),
// SERVICE NOW: SUBCATEGORY - DropdownMenu, Dynamically generated
// based on the SERVICE NOW: CATEGORY DropdownMenu
DropdownMenu(
onSelected: (value) {
setState(() {
Subcategory = value;
});
},
label: const Text("More Specifically..."),
expandedInsets: const EdgeInsets.all(1),
// Dynamically generated based on
dropdownMenuEntries: _specificProblemResourceMenuEntries),
const SizedBox(height: 12),
// SERVICE NOW: PRIORITY - 4/3 Toggle
Row(
children: [
const Text("Unable To work or sell a car?"),
const Spacer(),
Switch(
value: priorityBool,
onChanged: (bool value) {
setState(() {
if (value == true) {
priorityBool = true;
Priority = 3;
} else {
priorityBool = false;
Priority = 4;
}
});
},
),
],
),
const SizedBox(height: 12),
//SERVICE NOW: SHORT DESCRIPTION
TextFormField(
controller: titleShortdescriptionController,
decoration: const InputDecoration(
label: Text("Brief Description of Problem"),
),
onChanged: (value) {
setState(() {
ShortDescription = titleShortdescriptionController.text;
});
},
),
const SizedBox(height: 12),
//SERVICE NOW: DESCRIPTION
TextFormField(
controller: descriptionController,
decoration: const InputDecoration(
label: Text("Details Related To Your Problem")),
onChanged: (value) {
setState(() {
Description = descriptionController.text;
});
}),
const SizedBox(height: 12),
Padding(
padding: const EdgeInsets.all(32.0),
child: ButtonTheme(
minWidth: 100,
child: ElevatedButton(
onPressed: () async {
await createTicket(
callerId: Requestor,
category: Category,
subcategory: Subcategory,
assignmentGroup: AssignmentGroup,
priority: 3,
shortDescription: ShortDescription,
description: Description);
},
child: const Text("Submit")),
),
),
const Spacer(),
///
/// DEBUG: Verif all ticketfields required.
Text("DEBUG: TICKET FIELD VALUES",
style: GoogleFonts.lato(fontSize: 22)),
Text(Requestor),
Text(Category),
Text(Subcategory),
Text(AssignmentGroup),
Text(Priority.toString()),
Text(ShortDescription),
Text(Description),
],
));
}
}
Any Adivce or guidance would be MUCH Appreciated.
Upvotes: 0
Views: 77