Repository: parse-community/Parse-SDK-JS (JavaScript)

Issue:
Add custom headers to each server request
For apps hosted on Google App Engine there is an option to split traffic between servers. 

For instance you have an app backend which is updated and you want to only allow traffic from the new app version to visit the new backend version, this is called traffic splitting in GAE and can be done by adding a custom header to all TCP requests.

My app has a centralised function for executing all cloud functions ```Parse.Cloud.run()``` is there any way at all to add a custom header to this call so that I can split traffic between different backend versions ?

It would be great if this could be added somehow into Parse.initialise()

https://github.com/parse-community/Parse-SDK-JS/issues/603

https://github.com/parse-community/Parse-SDK-iOS-OSX/issues/877

https://github.com/parse-community/parse-server/issues/1059


Discussion:
I think there isn't a way to do that yet. Is this something that you'd be willed to tackle?
Awesome @dplewis ! Always there to save the day. Looking forward to trying this out later 
@dplewis Do you have an example of integrating this ? Ill try it out now 
```
// Initialize Parse
Parse.CoreManager.set('REQUEST_HEADERS', {});
// Run request
```

Please note these headers will be sent with every request.

Sorry for the late reply. For some reason this comment didn't send the first time.
Thanks man, just for anyone who stumbles on this and is using Typescript, there is no definition for ```CoreManager``` truthfully I had never even seen this mentioned in any docs so didnt realise it was a function. I maintain my own typescript definition file and push it into my parse projects, if anyone else does the same you can use Coremanager with the following 

```
  namespace CoreManager {
    function set(key: string, value: any): void;
    function get(key: string): void;        
  } 
```
Can you open a fix on the DefinatelyTyped repo?

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/parse/index.d.ts

I can update the docs with the core manager settings.
Sure ill give it a go, Ill see if I have any other things to add to it.
I have a problem when set the request headers:

`
Parse.CoreManager.set("REQUEST_HEADERS", {
  "x-domain": process.env.NEXT_PUBLIC_HEADERS,
});
`

When I add thar headers: the request only have this:

- Content-Type: text/plain
- Referer: http://localhost:3000/
- User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36
- x-domain: 7a6962d4

and the request show me this error:

`
Access to XMLHttpRequest at 'http://server/parse/health' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field x-domain is not allowed by Access-Control-Allow-Headers in preflight response.
`

But, when I remove the the Parse.CoreManager.set, the request headers show this:

- Accept: */*
- Accept-Encoding: gzip, deflate
- Accept-Language: es-419,es;q=0.9
- Cache-Control: no-cache
- Connection: keep-alive
- Content-Length: 183
- Content-Type: text/plain
- Host: vps262678.vps.ovh.ca
- Origin: http://localhost:3000
- Pragma: no-cache
- Referer: http://localhost:3000/
- User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36

and works againd
 
@ferchuhill - got the same error however all you need to do is add these additional headers in the `allowHeaders` array in the Parse server configuration like so:

```
parseServer = new ParseServer({
      databaseURI: cfg.databaseUri,
      cloud: process.env.CLOUD_CODE_MAIN || join(__dirname, '../cloud/main.js'),
      appId: cfg.appId,
      masterKey: cfg.masterKey,
      javascriptKey: cfg.javascriptKey,
      clientKey: cfg.clientKey,
      restAPIKey: cfg.restAPIKey,
      serverURL: cfg.localServerUrl,
      allowHeaders: ['x-domain'],
      ...
    });
```



Patch:
diff --git a/src/CoreManager.js b/src/CoreManager.js
index dc45d4314..4621dbcb1 100644
--- a/src/CoreManager.js
+++ b/src/CoreManager.js
@@ -171,6 +171,7 @@ const config: Config & { [key: string]: mixed } = {
             !!process.versions.node &&
             !process.versions.electron),
   REQUEST_ATTEMPT_LIMIT: 5,
+  REQUEST_HEADERS: {},
   SERVER_URL: 'https://api.parse.com/1',
   SERVER_AUTH_TYPE: null,
   SERVER_AUTH_TOKEN: null,
diff --git a/src/RESTController.js b/src/RESTController.js
index 4ebf31f08..f02d17e7c 100644
--- a/src/RESTController.js
+++ b/src/RESTController.js
@@ -48,7 +48,7 @@ if (typeof XDomainRequest !== 'undefined' &&
   useXDomainRequest = true;
 }
 
-function ajaxIE9(method: string, url: string, data: any, options?: FullOptions) {
+function ajaxIE9(method: string, url: string, data: any, headers?: any, options?: FullOptions) {
   return new Promise((resolve, reject) => {
     const xdr = new XDomainRequest();
     if (options && typeof options.requestTask === 'function') {
@@ -163,7 +163,10 @@ const RESTController = {
       if (CoreManager.get('SERVER_AUTH_TYPE') && CoreManager.get('SERVER_AUTH_TOKEN')) {
         headers['Authorization'] = CoreManager.get('SERVER_AUTH_TYPE') + ' ' + CoreManager.get('SERVER_AUTH_TOKEN');
       }
-
+      const customHeaders = CoreManager.get('REQUEST_HEADERS');
+      for (const key in customHeaders) {
+        headers[key] = customHeaders[key];
+      }
       if(options && typeof options.progress === 'function') {
         if (xhr.upload) {
           xhr.upload.addEventListener('progress', (oEvent) => {


Test patch:
diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js
index b4b8d711f..1d6874259 100644
--- a/src/__tests__/RESTController-test.js
+++ b/src/__tests__/RESTController-test.js
@@ -454,4 +454,21 @@ describe('RESTController', () => {
       done();
     });
   });
+
+  it('opens a XHR with the custom headers', () => {
+    CoreManager.set('REQUEST_HEADERS', { 'Cache-Control' : 'max-age=3600' });
+    const xhr = {
+      setRequestHeader: jest.fn(),
+      open: jest.fn(),
+      send: jest.fn()
+    };
+    RESTController._setXHR(function() { return xhr; });
+    RESTController.ajax('GET', 'users/me', {}, { 'X-Parse-Session-Token': '123' });
+    expect(xhr.setRequestHeader.mock.calls[3]).toEqual(
+      [ 'Cache-Control', 'max-age=3600' ]
+    );
+    expect(xhr.open.mock.calls[0]).toEqual([ 'GET', 'users/me', true ]);
+    expect(xhr.send.mock.calls[0][0]).toEqual({});
+    CoreManager.set('REQUEST_HEADERS', {});
+  });
 });