I get a CORS error when I'm trying to download and use a Cloudinary widget javascript resource with the Angular HttpClient.
What is going on? I'm not new to HttpClient or CORS but have never seen this.
Access to XMLHttpRequest at 'https://widget.cloudinary.com/v2.0/global/all.js' from origin 'http://127.0.0.1:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
The server CORS has nothing to do with this question. It is publicly available code and can easily be retrieved in any browser.
The requested script does arrive in my Chrome Dev Tools / Network tab XHR section. So the server sent it and Chrome happily received it.
I'm in the Angular dev environment.
The problem is with Angular and I think HttpClient. It thinks there is a CORS issue that doesn't exist.
Request headers in Dev Tools: Sec-Fetch-Mode: cors
I've looked at a bunch of other SO posts, including one that appears to be similar, but no help.
My code.
export class CloudinaryComponent implements OnInit {
private url = 'https://widget.cloudinary.com/v2.0/global/all.js';
constructor(
private http: HttpClient
) {}
ngOnInit() {
this.loadWidget;
}
// Load the Cloudinary Upload Widget code, not the widget GUI.
private loadWidget() {
return this.http.get(this.url);
};
// Button click calls the Upload Widget GUI.
private callPopup() {
this.loadWidget().subscribe( result => {
// this.uploadWidget.open();
});
}
}
How can I embed the Cloudinary widget properly?
Don't dismiss the error message. The CORS issue has to do with the server not providing the right header to allow cross origin access from your websites javascript code! As the error says
No 'Access-Control-Allow-Origin' header is present on the requested resource.
The problem isn't your client side. Angular isn't creating this error. Your browser is blocking the request for security reasons. The server just decided not to allow those kind of requests. The fact that you can access the resource directly from your browser might fool you, but in this case your accessing the resource in a different way (i.e not from a javascript XMLHttpRequest).
Read up on CORS if you want to understand it fully:
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
XMLHttpRequest cannot load XXX No 'Access-Control-Allow-Origin' header
Downloading the javascript file with the HttpClient won't enable you to use the widget! You have to embed the cloudinary js file in your html to be able to use it.
A Service embeding and creating javascript widgets dynamically could look like this:
import { Injectable, RendererFactory2, Renderer2 } from '@angular/core';
import { Observable, of, fromEvent } from 'rxjs';
import { map } from 'rxjs/operators';
declare let cloudinary: any; // declare js widget variable
const widgetUrl = 'https://widget.cloudinary.com/v2.0/global/all.js';
@Injectable({
providedIn: 'root'
})
export class CloudinaryService {
private renderer: Renderer2;
constructor(rendererFactory: RendererFactory2) {
this.renderer = rendererFactory.createRenderer(null, null);
}
// create the upload widget
createUploadWidget(data: any, callback: (error: any, result: any) => void): Observable<any> {
return this.skriptExists(widgetUrl)
// js is embeded -> call js function directly
? of(cloudinary.createUploadWidget(data, callback))
// js isn't embeded -> embed js file and wait for it to load
: fromEvent(this.addJsToElement(widgetUrl), 'load').pipe(
// map to call of js function
map(e => cloudinary.createUploadWidget(data, callback))
);
}
// check if js file is already embeded
private skriptExists(jsUrl: string): boolean {
return document.querySelector(`script[src="${jsUrl}"]`) ? true : false;
}
// embed external js file in html
private addJsToElement(jsUrl: string): HTMLScriptElement {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = jsUrl;
this.renderer.appendChild(document.body, script);
return script;
}
}
Use the Service in your Component to create the widget:
export class AppComponent implements OnInit {
widget: any;
constructor(private cloudinary: CloudinaryService) { }
ngOnInit() {
this.cloudinary.createUploadWidget(
{
cloudName: 'my_cloud_name',
uploadPreset: 'my_preset'
},
(error, result) => {
if (!error && result && result.event === "success") {
console.log('Done! Here is the image info: ', result.info);
}
}
).subscribe(widget => this.widget = widget);
}
openWidget() {
if (this.widget) {
console.log('open')
this.widget.open();
}
}
}