Lol, this is rather funny because I had this issue from day one of using directus sdk.
Using the standard directus client with export const directus = new Directus('https://blu.bb');
results in CORS errors for all requests as axios seems to add a X-XSRF-TOKEN
header.
Browser console:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://blu.bb/users/me. (Reason: header ‘x-xsrf-token’ is not allowed according to header ‘Access-Control-Allow-Headers’ from CORS preflight response).
Axios throws this error:
{
"parent": {
"message": "Network Error",
"name": "AxiosError",
"stack": "",
"config": {
"transitional": {
"silentJSONParsing": true,
"forcedJSONParsing": true,
"clarifyTimeoutError": false
},
"transformRequest": [
null
],
"transformResponse": [
null
],
"timeout": 0,
"xsrfCookieName": "XSRF-TOKEN",
"xsrfHeaderName": "X-XSRF-TOKEN",
"maxContentLength": -1,
"maxBodyLength": -1,
"env": {
"FormData": null
},
"headers": {
"Accept": "application/json, text/plain, */*",
"Authorization": "",
"X-XSRF-TOKEN": "8e5744ca-8f3a-4a6b-9a6c-2ba8006b7423"
},
"baseURL": "https://blu.bb",
"withCredentials": true,
"method": "get",
"url": "/users/me"
},
"code": "ERR_NETWORK",
"status": null
},
"response": {
"status": 0,
"statusText": ""
},
"errors": []
}
I guess I can work around by setting CORS_ALLOWED_HEADERS
but in my opinion this should work out of the box (or at least it should be documented somewhere).
Regardless, as I use SvelteKit in my projects with ssr I needed to use fetch
and implemented a FetchTransport
which replaces the default transport. I even wrote a simple rollup plugin that gets rid of the default transport to prevent shipping axios.
`rollup-plugin-directus.js`
import * as walk from 'acorn-walk';
import MagicString from 'magic-string';
/** @type {import('rollup').Plugin} */
const directus = {
name: 'rollup-plugin-directus',
resolveId: {
handler(id, importer) {
if (
id === 'axios' &&
importer &&
importer.includes('@directus') &&
importer.includes('sdk')
) {
return {
id: 'axios',
external: true,
moduleSideEffects: false,
};
}
},
},
transform(code, id) {
if (id.includes('directus') && id.includes('sdk')) {
const ast = this.parse(code);
const s = new MagicString(code, { filename: id });
walk.simple(ast, {
ClassDeclaration(node) {
if (isClassDeclaration(node) && node.id?.name === 'Transport') {
s.remove(node.start, node.end);
}
},
ExportNamedDeclaration(node) {
if (isExportNamedDeclaration(node)) {
for (let idx = 0; idx < node.specifiers.length; idx++) {
const spec = node.specifiers[idx];
if (isExportSpecifier(spec) && spec.local.name === 'Transport') {
s.remove(spec.start, spec.end);
// Comma if not the last item.
if (idx < node.specifiers.length - 1) {
s.remove(spec.end, spec.end + 1);
}
break;
}
}
}
},
});
return {
code: s.toString(),
map: s.generateMap({ source: id }),
};
}
},
};
export default directus;
/** @typedef {Parameters<import('acorn-walk').SimpleWalkerFn<unknown>>[0]} AcornNode */
/**
* @param {{ type: string }} node
* @returns {node is AcornNode & import('estree').ClassDeclaration}
*/
const isClassDeclaration = (node) => {
return node.type === 'ClassDeclaration';
};
/**
* @param {{ type: string }} node
* @returns {node is AcornNode & import('estree').ExportNamedDeclaration}
*/
const isExportNamedDeclaration = (node) => {
return node.type === 'ExportNamedDeclaration';
};
/**
* @param {{ type: string }} node
* @returns {node is AcornNode & import('estree').ExportSpecifier}
*/
const isExportSpecifier = (node) => {
return node.type === 'ExportSpecifier';
};
I already did this in two projects and for the new one I want to do it correctly. In the veins of
(also fwiw, I've been meaning to refactor out axios
of the SDK altogether to make it as lean as possible, but lets not get ahead of ourselves there and figure this out first).
Originally posted by @rijkvanzanten in https://github.com/directus/sdk/issues/26#issuecomment-1204381041
I want to push the splitting of the transport forward.
I think this would be a breaking change as I would love to generate separate bundles for each transport (to be able to mark axios as optional). But this would require changing user side import I guess (except something with dynamic imports to get the axios transport if axios
is installed and no other transport is provided :thinking:).
For backward compatibility we could let the directus constructor accept string | ITransport
as first argument and deprecate passing the transport in the options object. If it is a string the constructor would try to create the axios transport (via dynamic import?) and throw if this fails (i.e. axios
not available). Else the constructor parameter is just used as the transport.
Probably the hardest part is to add the auth token in a transport agnostic way. But we could help this by pushing the responsibility for this to the developer but giving them a hand by providing a helper function for this.
@rijkvanzanten what do you think?