@servicetitan/form 34.0.0 → 35.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=uploader.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploader.test.d.ts","sourceRoot":"","sources":["../../../src/file-uploader/__tests__/uploader.test.ts"],"names":[],"mappings":""}
@@ -1,5 +1,6 @@
1
1
  import type { FileUploaderConfig } from './config';
2
2
  export declare class Uploader {
3
+ #private;
3
4
  private readonly resumable;
4
5
  constructor(config?: FileUploaderConfig, handlers?: {
5
6
  progress?(file: File, progress: number): void;
@@ -1 +1 @@
1
- {"version":3,"file":"uploader.d.ts","sourceRoot":"","sources":["../../src/file-uploader/uploader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAsBnD,qBAAa,QAAQ;IACjB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;gBAGlC,MAAM,GAAE,kBAAuB,EAC/B,QAAQ,GAAE;QACN,QAAQ,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9C,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7C,KAAK,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACzC,EACN,UAAU,SAAY;IA4D1B,MAAM,CAAC,MAAM,EAAE,IAAI,GAAG,QAAQ;IAU9B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI;CAYrB"}
1
+ {"version":3,"file":"uploader.d.ts","sourceRoot":"","sources":["../../src/file-uploader/uploader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAsBnD,qBAAa,QAAQ;;IACjB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;gBAGlC,MAAM,GAAE,kBAAuB,EAC/B,QAAQ,GAAE;QACN,QAAQ,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9C,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7C,KAAK,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACzC,EACN,UAAU,SAAY;IA+D1B,MAAM,CAAC,MAAM,EAAE,IAAI,GAAG,QAAQ;IAU9B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI;CAwBrB"}
@@ -1,3 +1,18 @@
1
+ function _check_private_redeclaration(obj, privateCollection) {
2
+ if (privateCollection.has(obj)) {
3
+ throw new TypeError("Cannot initialize the same private elements twice on an object");
4
+ }
5
+ }
6
+ function _class_private_method_get(receiver, privateSet, fn) {
7
+ if (!privateSet.has(receiver)) {
8
+ throw new TypeError("attempted to get private field on non-instance");
9
+ }
10
+ return fn;
11
+ }
12
+ function _class_private_method_init(obj, privateSet) {
13
+ _check_private_redeclaration(obj, privateSet);
14
+ privateSet.add(obj);
15
+ }
1
16
  function _define_property(obj, key, value) {
2
17
  if (key in obj) {
3
18
  Object.defineProperty(obj, key, {
@@ -11,8 +26,10 @@ function _define_property(obj, key, value) {
11
26
  }
12
27
  return obj;
13
28
  }
29
+ import Cookies from 'js-cookie';
14
30
  import { v4 as uuid } from 'uuid';
15
31
  const Resumable = require('resumablejs');
32
+ var _getCsrfTokenHeader = /*#__PURE__*/ new WeakSet();
16
33
  export class Uploader {
17
34
  upload(source) {
18
35
  if (source instanceof File) {
@@ -36,6 +53,7 @@ export class Uploader {
36
53
  }
37
54
  }
38
55
  constructor(config = {}, handlers = {}, folderName = 'default'){
56
+ _class_private_method_init(this, _getCsrfTokenHeader);
39
57
  _define_property(this, "resumable", void 0);
40
58
  const { errorParser, ...configuration } = config;
41
59
  const resumable = new Resumable({
@@ -43,7 +61,8 @@ export class Uploader {
43
61
  testChunks: false,
44
62
  simultaneousUploads: 1,
45
63
  headers: {
46
- 'X-Requested-With': 'XMLHttpRequest'
64
+ 'X-Requested-With': 'XMLHttpRequest',
65
+ ..._class_private_method_get(this, _getCsrfTokenHeader, getCsrfTokenHeader).call(this)
47
66
  },
48
67
  permanentErrors: [
49
68
  302,
@@ -90,5 +109,15 @@ export class Uploader {
90
109
  this.resumable = resumable;
91
110
  }
92
111
  }
112
+ function getCsrfTokenHeader() {
113
+ const csrfTokenName = 'X-CSRF-Token';
114
+ const token = Cookies.get(csrfTokenName);
115
+ if (token) {
116
+ return {
117
+ [csrfTokenName]: token
118
+ };
119
+ }
120
+ return {};
121
+ }
93
122
 
94
123
  //# sourceMappingURL=uploader.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/file-uploader/uploader.ts"],"sourcesContent":["import { v4 as uuid } from 'uuid';\n\nimport type { FileUploaderConfig } from './config';\n\nconst Resumable = require('resumablejs');\n\ninterface Resumable {\n files: ResumableFile[];\n on(event: 'fileAdded', callback: (file: ResumableFile, event: Event) => void): void;\n on(event: 'fileProgress', callback: (file: ResumableFile, message?: string) => void): void;\n on(event: 'fileSuccess', callback: (file: ResumableFile, message?: string) => void): void;\n on(event: 'fileError', callback: (file: ResumableFile, message?: string) => void): void;\n addFile(file: File): void;\n upload(): void;\n cancel(): void;\n}\n\ninterface ResumableFile {\n file: File;\n progress(relative?: boolean): number;\n isComplete(): boolean;\n cancel(): void;\n}\n\nexport class Uploader {\n private readonly resumable: Resumable;\n\n constructor(\n config: FileUploaderConfig = {},\n handlers: {\n progress?(file: File, progress: number): void;\n success?(file: File, message?: string): void;\n error?(file: File, message?: string): void;\n } = {},\n folderName = 'default'\n ) {\n const { errorParser, ...configuration } = config;\n\n const resumable: Resumable = new Resumable({\n target: `/app/api/fileuploader/folders/${folderName}/files`,\n testChunks: false,\n simultaneousUploads: 1,\n headers: { 'X-Requested-With': 'XMLHttpRequest' },\n permanentErrors: [302, 400, 404, 415, 500, 501],\n generateUniqueIdentifier: () => uuid(),\n fileTypeErrorCallback: (file: File) => {\n handlers.error?.(\n file,\n `File type not allowed. Please provide file of type ${configuration.fileType\n ?.map(ext => `.${ext}`)\n ?.join(', ')}.`\n );\n },\n ...configuration,\n });\n\n resumable.on('fileAdded', () => {\n resumable.upload();\n });\n\n resumable.on('fileProgress', resumableFile => {\n if (resumableFile.isComplete()) {\n return;\n }\n\n let progress = Math.floor(resumableFile.progress(false) * 100);\n if (progress > 99) {\n progress = 99;\n }\n\n if (handlers.progress) {\n handlers.progress(resumableFile.file, progress);\n }\n });\n\n resumable.on('fileSuccess', (resumableFile, message) => {\n if (handlers.success) {\n handlers.success(resumableFile.file, message);\n }\n\n resumableFile.cancel();\n });\n\n resumable.on('fileError', (resumableFile, message) => {\n if (handlers.error) {\n handlers.error(resumableFile.file, errorParser ? errorParser(message) : message);\n }\n\n resumableFile.cancel();\n });\n\n this.resumable = resumable;\n }\n\n upload(source: File | FileList) {\n if (source instanceof File) {\n this.resumable.addFile(source);\n } else {\n for (const file of Array.from(source)) {\n this.resumable.addFile(file);\n }\n }\n }\n\n cancel(file?: File) {\n if (file) {\n for (const resumableFile of this.resumable.files) {\n if (resumableFile.file === file) {\n resumableFile.cancel();\n break;\n }\n }\n } else {\n this.resumable.cancel();\n }\n }\n}\n"],"names":["v4","uuid","Resumable","require","Uploader","upload","source","File","resumable","addFile","file","Array","from","cancel","resumableFile","files","config","handlers","folderName","errorParser","configuration","target","testChunks","simultaneousUploads","headers","permanentErrors","generateUniqueIdentifier","fileTypeErrorCallback","error","fileType","map","ext","join","on","isComplete","progress","Math","floor","message","success"],"mappings":";;;;;;;;;;;;;AAAA,SAASA,MAAMC,IAAI,QAAQ,OAAO;AAIlC,MAAMC,YAAYC,QAAQ;AAoB1B,OAAO,MAAMC;IAsETC,OAAOC,MAAuB,EAAE;QAC5B,IAAIA,kBAAkBC,MAAM;YACxB,IAAI,CAACC,SAAS,CAACC,OAAO,CAACH;QAC3B,OAAO;YACH,KAAK,MAAMI,QAAQC,MAAMC,IAAI,CAACN,QAAS;gBACnC,IAAI,CAACE,SAAS,CAACC,OAAO,CAACC;YAC3B;QACJ;IACJ;IAEAG,OAAOH,IAAW,EAAE;QAChB,IAAIA,MAAM;YACN,KAAK,MAAMI,iBAAiB,IAAI,CAACN,SAAS,CAACO,KAAK,CAAE;gBAC9C,IAAID,cAAcJ,IAAI,KAAKA,MAAM;oBAC7BI,cAAcD,MAAM;oBACpB;gBACJ;YACJ;QACJ,OAAO;YACH,IAAI,CAACL,SAAS,CAACK,MAAM;QACzB;IACJ;IAxFA,YACIG,SAA6B,CAAC,CAAC,EAC/BC,WAII,CAAC,CAAC,EACNC,aAAa,SAAS,CACxB;QAVF,uBAAiBV,aAAjB,KAAA;QAWI,MAAM,EAAEW,WAAW,EAAE,GAAGC,eAAe,GAAGJ;QAE1C,MAAMR,YAAuB,IAAIN,UAAU;YACvCmB,QAAQ,CAAC,8BAA8B,EAAEH,WAAW,MAAM,CAAC;YAC3DI,YAAY;YACZC,qBAAqB;YACrBC,SAAS;gBAAE,oBAAoB;YAAiB;YAChDC,iBAAiB;gBAAC;gBAAK;gBAAK;gBAAK;gBAAK;gBAAK;aAAI;YAC/CC,0BAA0B,IAAMzB;YAChC0B,uBAAuB,CAACjB;oBAGsCU,6BAAAA,yBAF1DH;iBAAAA,kBAAAA,SAASW,KAAK,cAAdX,sCAAAA,qBAAAA,UACIP,MACA,CAAC,mDAAmD,GAAEU,0BAAAA,cAAcS,QAAQ,cAAtBT,+CAAAA,8BAAAA,wBAChDU,GAAG,CAACC,CAAAA,MAAO,CAAC,CAAC,EAAEA,KAAK,eAD4BX,kDAAAA,4BAEhDY,IAAI,CAAC,MAAM,CAAC,CAAC;YAE3B;YACA,GAAGZ,aAAa;QACpB;QAEAZ,UAAUyB,EAAE,CAAC,aAAa;YACtBzB,UAAUH,MAAM;QACpB;QAEAG,UAAUyB,EAAE,CAAC,gBAAgBnB,CAAAA;YACzB,IAAIA,cAAcoB,UAAU,IAAI;gBAC5B;YACJ;YAEA,IAAIC,WAAWC,KAAKC,KAAK,CAACvB,cAAcqB,QAAQ,CAAC,SAAS;YAC1D,IAAIA,WAAW,IAAI;gBACfA,WAAW;YACf;YAEA,IAAIlB,SAASkB,QAAQ,EAAE;gBACnBlB,SAASkB,QAAQ,CAACrB,cAAcJ,IAAI,EAAEyB;YAC1C;QACJ;QAEA3B,UAAUyB,EAAE,CAAC,eAAe,CAACnB,eAAewB;YACxC,IAAIrB,SAASsB,OAAO,EAAE;gBAClBtB,SAASsB,OAAO,CAACzB,cAAcJ,IAAI,EAAE4B;YACzC;YAEAxB,cAAcD,MAAM;QACxB;QAEAL,UAAUyB,EAAE,CAAC,aAAa,CAACnB,eAAewB;YACtC,IAAIrB,SAASW,KAAK,EAAE;gBAChBX,SAASW,KAAK,CAACd,cAAcJ,IAAI,EAAES,cAAcA,YAAYmB,WAAWA;YAC5E;YAEAxB,cAAcD,MAAM;QACxB;QAEA,IAAI,CAACL,SAAS,GAAGA;IACrB;AAwBJ"}
1
+ {"version":3,"sources":["../../src/file-uploader/uploader.ts"],"sourcesContent":["import Cookies from 'js-cookie';\nimport { v4 as uuid } from 'uuid';\n\nimport type { FileUploaderConfig } from './config';\n\nconst Resumable = require('resumablejs');\n\ninterface Resumable {\n files: ResumableFile[];\n on(event: 'fileAdded', callback: (file: ResumableFile, event: Event) => void): void;\n on(event: 'fileProgress', callback: (file: ResumableFile, message?: string) => void): void;\n on(event: 'fileSuccess', callback: (file: ResumableFile, message?: string) => void): void;\n on(event: 'fileError', callback: (file: ResumableFile, message?: string) => void): void;\n addFile(file: File): void;\n upload(): void;\n cancel(): void;\n}\n\ninterface ResumableFile {\n file: File;\n progress(relative?: boolean): number;\n isComplete(): boolean;\n cancel(): void;\n}\n\nexport class Uploader {\n private readonly resumable: Resumable;\n\n constructor(\n config: FileUploaderConfig = {},\n handlers: {\n progress?(file: File, progress: number): void;\n success?(file: File, message?: string): void;\n error?(file: File, message?: string): void;\n } = {},\n folderName = 'default'\n ) {\n const { errorParser, ...configuration } = config;\n\n const resumable: Resumable = new Resumable({\n target: `/app/api/fileuploader/folders/${folderName}/files`,\n testChunks: false,\n simultaneousUploads: 1,\n headers: {\n 'X-Requested-With': 'XMLHttpRequest',\n ...this.#getCsrfTokenHeader(),\n },\n permanentErrors: [302, 400, 404, 415, 500, 501],\n generateUniqueIdentifier: () => uuid(),\n fileTypeErrorCallback: (file: File) => {\n handlers.error?.(\n file,\n `File type not allowed. Please provide file of type ${configuration.fileType\n ?.map(ext => `.${ext}`)\n ?.join(', ')}.`\n );\n },\n ...configuration,\n });\n\n resumable.on('fileAdded', () => {\n resumable.upload();\n });\n\n resumable.on('fileProgress', resumableFile => {\n if (resumableFile.isComplete()) {\n return;\n }\n\n let progress = Math.floor(resumableFile.progress(false) * 100);\n if (progress > 99) {\n progress = 99;\n }\n\n if (handlers.progress) {\n handlers.progress(resumableFile.file, progress);\n }\n });\n\n resumable.on('fileSuccess', (resumableFile, message) => {\n if (handlers.success) {\n handlers.success(resumableFile.file, message);\n }\n\n resumableFile.cancel();\n });\n\n resumable.on('fileError', (resumableFile, message) => {\n if (handlers.error) {\n handlers.error(resumableFile.file, errorParser ? errorParser(message) : message);\n }\n\n resumableFile.cancel();\n });\n\n this.resumable = resumable;\n }\n\n upload(source: File | FileList) {\n if (source instanceof File) {\n this.resumable.addFile(source);\n } else {\n for (const file of Array.from(source)) {\n this.resumable.addFile(file);\n }\n }\n }\n\n cancel(file?: File) {\n if (file) {\n for (const resumableFile of this.resumable.files) {\n if (resumableFile.file === file) {\n resumableFile.cancel();\n break;\n }\n }\n } else {\n this.resumable.cancel();\n }\n }\n\n #getCsrfTokenHeader() {\n const csrfTokenName = 'X-CSRF-Token';\n const token = Cookies.get(csrfTokenName);\n if (token) {\n return {\n [csrfTokenName]: token,\n };\n }\n\n return {};\n }\n}\n"],"names":["Cookies","v4","uuid","Resumable","require","Uploader","upload","source","File","resumable","addFile","file","Array","from","cancel","resumableFile","files","config","handlers","folderName","errorParser","configuration","target","testChunks","simultaneousUploads","headers","permanentErrors","generateUniqueIdentifier","fileTypeErrorCallback","error","fileType","map","ext","join","on","isComplete","progress","Math","floor","message","success","csrfTokenName","token","get"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servicetitan/form",
3
- "version": "34.0.0",
3
+ "version": "35.1.0",
4
4
  "description": "",
5
5
  "homepage": "https://docs.st.dev/docs/frontend/form",
6
6
  "repository": {
@@ -17,14 +17,15 @@
17
17
  ],
18
18
  "devDependencies": {
19
19
  "@progress/kendo-react-dateinputs": "~5.5.0",
20
- "@servicetitan/confirm": "^34.0.0",
21
- "@servicetitan/culture": "^34.0.0",
20
+ "@servicetitan/confirm": "^35.1.0",
21
+ "@servicetitan/culture": "^35.1.0",
22
22
  "@servicetitan/design-system": "~14.5.1",
23
- "@servicetitan/form-state": "^34.0.0",
23
+ "@servicetitan/form-state": "^35.1.0",
24
24
  "@servicetitan/hash-browser-router": "^32.2.0",
25
25
  "@servicetitan/react-ioc": "^32.2.0",
26
26
  "@servicetitan/tokens": ">=12.2.1",
27
27
  "@servicetitan/web-components": "^32.2.0",
28
+ "@types/js-cookie": "^3.0.3",
28
29
  "@types/react": "~18.2.55",
29
30
  "@types/react-input-mask": "~2.0.5",
30
31
  "@types/uuid": "~8.3.4",
@@ -38,10 +39,10 @@
38
39
  },
39
40
  "peerDependencies": {
40
41
  "@progress/kendo-react-dateinputs": "~5.5.0",
41
- "@servicetitan/confirm": "^34.0.0",
42
- "@servicetitan/culture": "^34.0.0",
42
+ "@servicetitan/confirm": "^35.1.0",
43
+ "@servicetitan/culture": "^35.1.0",
43
44
  "@servicetitan/design-system": ">=13.2.1",
44
- "@servicetitan/form-state": "^34.0.0",
45
+ "@servicetitan/form-state": "^35.1.0",
45
46
  "@servicetitan/react-ioc": ">21.0.0",
46
47
  "@servicetitan/tokens": ">=12.2.1",
47
48
  "accounting": "~0.4.1",
@@ -55,6 +56,7 @@
55
56
  "dependencies": {
56
57
  "classnames": "^2.3.1",
57
58
  "debounce": "^1.2.1",
59
+ "js-cookie": "^3.0.5",
58
60
  "react-input-mask": "~2.0.4",
59
61
  "uuid": "~8.3.2"
60
62
  },
@@ -65,5 +67,5 @@
65
67
  "less": true,
66
68
  "webpack": false
67
69
  },
68
- "gitHead": "39c8bee0e01ce314dbdd07049e1d0851a03225ab"
70
+ "gitHead": "2309437f0c7867ef198487676c1b02667b30f057"
69
71
  }
@@ -0,0 +1,125 @@
1
+ import { FileUploaderConfig } from '../config';
2
+ import { Uploader } from '../uploader';
3
+
4
+ const mockResumableConstructor = jest.fn();
5
+ jest.mock('resumablejs', () => {
6
+ return jest.fn().mockImplementation(config => {
7
+ mockResumableConstructor(config);
8
+ return {
9
+ files: [],
10
+ on: jest.fn(),
11
+ addFile: jest.fn(),
12
+ upload: jest.fn(),
13
+ cancel: jest.fn(),
14
+ };
15
+ });
16
+ });
17
+
18
+ jest.mock('uuid', () => ({
19
+ v4: jest.fn(() => 'mock-uuid'),
20
+ }));
21
+
22
+ describe('[form] Uploader', () => {
23
+ const CSRF_TOKEN = 'test-token-123';
24
+ let originalDocumentCookie: string;
25
+ let uploaderParams: FileUploaderConfig;
26
+
27
+ function setCookie(value: string) {
28
+ Object.defineProperty(document, 'cookie', {
29
+ writable: true,
30
+ value,
31
+ });
32
+ }
33
+
34
+ beforeEach(() => {
35
+ originalDocumentCookie = document.cookie;
36
+ document.cookie = '';
37
+ uploaderParams = {};
38
+ setCookie(`X-CSRF-Token=${CSRF_TOKEN}; other-cookie=value`);
39
+ jest.clearAllMocks();
40
+ });
41
+
42
+ afterEach(() => {
43
+ setCookie(originalDocumentCookie);
44
+ });
45
+
46
+ function subject() {
47
+ return new Uploader(uploaderParams);
48
+ }
49
+
50
+ function expectHeaders(expectedHeaders: Record<string, string>) {
51
+ expect(mockResumableConstructor).toHaveBeenCalledWith(
52
+ expect.objectContaining({
53
+ headers: expect.objectContaining(expectedHeaders),
54
+ })
55
+ );
56
+ }
57
+
58
+ function itIncludesCSRFTokenHeader() {
59
+ test('includes CSRF token header', () => {
60
+ subject();
61
+
62
+ expectHeaders({
63
+ 'X-Requested-With': 'XMLHttpRequest',
64
+ 'X-CSRF-Token': CSRF_TOKEN,
65
+ });
66
+ });
67
+ }
68
+
69
+ function itDoesNotIncludeCSRFTokenHeader() {
70
+ test('does not include CSRF token header', () => {
71
+ subject();
72
+
73
+ expectHeaders({
74
+ 'X-Requested-With': 'XMLHttpRequest',
75
+ });
76
+ });
77
+ }
78
+
79
+ itIncludesCSRFTokenHeader();
80
+
81
+ describe('when CSRF token cookie does not exist', () => {
82
+ beforeEach(() => {
83
+ setCookie('other-cookie=value; another-cookie=another-value');
84
+ });
85
+
86
+ itDoesNotIncludeCSRFTokenHeader();
87
+ });
88
+
89
+ describe('when CSRF token cookie exists but has no value', () => {
90
+ beforeEach(() => {
91
+ setCookie('x-csrf-token=; other-cookie=value');
92
+ });
93
+
94
+ itDoesNotIncludeCSRFTokenHeader();
95
+ });
96
+
97
+ describe('when CSRF token is URL encoded', () => {
98
+ beforeEach(() => {
99
+ const encodedToken = CSRF_TOKEN.replaceAll('-', '%2D');
100
+ setCookie(`X-CSRF-Token=${encodedToken}; other-cookie=value`);
101
+ });
102
+
103
+ itIncludesCSRFTokenHeader();
104
+ });
105
+
106
+ describe('when custom headers are provided', () => {
107
+ beforeEach(() => {
108
+ uploaderParams = {
109
+ headers: {
110
+ 'Custom-Header': 'custom-value',
111
+ },
112
+ };
113
+ });
114
+
115
+ test('custom headers only are passed through config', () => {
116
+ setCookie('x-csrf-token=test-token-123');
117
+
118
+ subject();
119
+
120
+ expectHeaders({
121
+ 'Custom-Header': 'custom-value',
122
+ });
123
+ });
124
+ });
125
+ });
@@ -1,3 +1,4 @@
1
+ import Cookies from 'js-cookie';
1
2
  import { v4 as uuid } from 'uuid';
2
3
 
3
4
  import type { FileUploaderConfig } from './config';
@@ -40,7 +41,10 @@ export class Uploader {
40
41
  target: `/app/api/fileuploader/folders/${folderName}/files`,
41
42
  testChunks: false,
42
43
  simultaneousUploads: 1,
43
- headers: { 'X-Requested-With': 'XMLHttpRequest' },
44
+ headers: {
45
+ 'X-Requested-With': 'XMLHttpRequest',
46
+ ...this.#getCsrfTokenHeader(),
47
+ },
44
48
  permanentErrors: [302, 400, 404, 415, 500, 501],
45
49
  generateUniqueIdentifier: () => uuid(),
46
50
  fileTypeErrorCallback: (file: File) => {
@@ -114,4 +118,16 @@ export class Uploader {
114
118
  this.resumable.cancel();
115
119
  }
116
120
  }
121
+
122
+ #getCsrfTokenHeader() {
123
+ const csrfTokenName = 'X-CSRF-Token';
124
+ const token = Cookies.get(csrfTokenName);
125
+ if (token) {
126
+ return {
127
+ [csrfTokenName]: token,
128
+ };
129
+ }
130
+
131
+ return {};
132
+ }
117
133
  }