DJ K
DJ K

Reputation: 1

Flutter and HTTP Module: Cannot POST to ServiceNow Endpoint: Error: ClientException: XMLHttpRequest error

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

Answers (0)

Related Questions