@uploadista/client-core 0.0.3

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.
Files changed (235) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +100 -0
  4. package/dist/auth/auth-http-client.d.ts +50 -0
  5. package/dist/auth/auth-http-client.d.ts.map +1 -0
  6. package/dist/auth/auth-http-client.js +110 -0
  7. package/dist/auth/direct-auth.d.ts +38 -0
  8. package/dist/auth/direct-auth.d.ts.map +1 -0
  9. package/dist/auth/direct-auth.js +95 -0
  10. package/dist/auth/index.d.ts +6 -0
  11. package/dist/auth/index.d.ts.map +1 -0
  12. package/dist/auth/index.js +5 -0
  13. package/dist/auth/no-auth.d.ts +26 -0
  14. package/dist/auth/no-auth.d.ts.map +1 -0
  15. package/dist/auth/no-auth.js +33 -0
  16. package/dist/auth/saas-auth.d.ts +80 -0
  17. package/dist/auth/saas-auth.d.ts.map +1 -0
  18. package/dist/auth/saas-auth.js +167 -0
  19. package/dist/auth/types.d.ts +101 -0
  20. package/dist/auth/types.d.ts.map +1 -0
  21. package/dist/auth/types.js +8 -0
  22. package/dist/chunk-buffer.d.ts +209 -0
  23. package/dist/chunk-buffer.d.ts.map +1 -0
  24. package/dist/chunk-buffer.js +236 -0
  25. package/dist/client/create-uploadista-client.d.ts +369 -0
  26. package/dist/client/create-uploadista-client.d.ts.map +1 -0
  27. package/dist/client/create-uploadista-client.js +518 -0
  28. package/dist/client/index.d.ts +4 -0
  29. package/dist/client/index.d.ts.map +1 -0
  30. package/dist/client/index.js +3 -0
  31. package/dist/client/uploadista-api.d.ts +284 -0
  32. package/dist/client/uploadista-api.d.ts.map +1 -0
  33. package/dist/client/uploadista-api.js +444 -0
  34. package/dist/client/uploadista-websocket-manager.d.ts +110 -0
  35. package/dist/client/uploadista-websocket-manager.d.ts.map +1 -0
  36. package/dist/client/uploadista-websocket-manager.js +207 -0
  37. package/dist/error.d.ts +106 -0
  38. package/dist/error.d.ts.map +1 -0
  39. package/dist/error.js +69 -0
  40. package/dist/index.d.ts +9 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +12 -0
  43. package/dist/logger.d.ts +70 -0
  44. package/dist/logger.d.ts.map +1 -0
  45. package/dist/logger.js +59 -0
  46. package/dist/mock-data-store.d.ts +30 -0
  47. package/dist/mock-data-store.d.ts.map +1 -0
  48. package/dist/mock-data-store.js +88 -0
  49. package/dist/network-monitor.d.ts +262 -0
  50. package/dist/network-monitor.d.ts.map +1 -0
  51. package/dist/network-monitor.js +291 -0
  52. package/dist/services/abort-controller-service.d.ts +19 -0
  53. package/dist/services/abort-controller-service.d.ts.map +1 -0
  54. package/dist/services/abort-controller-service.js +4 -0
  55. package/dist/services/checksum-service.d.ts +4 -0
  56. package/dist/services/checksum-service.d.ts.map +1 -0
  57. package/dist/services/checksum-service.js +1 -0
  58. package/dist/services/file-reader-service.d.ts +38 -0
  59. package/dist/services/file-reader-service.d.ts.map +1 -0
  60. package/dist/services/file-reader-service.js +4 -0
  61. package/dist/services/fingerprint-service.d.ts +4 -0
  62. package/dist/services/fingerprint-service.d.ts.map +1 -0
  63. package/dist/services/fingerprint-service.js +1 -0
  64. package/dist/services/http-client.d.ts +182 -0
  65. package/dist/services/http-client.d.ts.map +1 -0
  66. package/dist/services/http-client.js +1 -0
  67. package/dist/services/id-generation-service.d.ts +10 -0
  68. package/dist/services/id-generation-service.d.ts.map +1 -0
  69. package/dist/services/id-generation-service.js +1 -0
  70. package/dist/services/index.d.ts +11 -0
  71. package/dist/services/index.d.ts.map +1 -0
  72. package/dist/services/index.js +10 -0
  73. package/dist/services/platform-service.d.ts +48 -0
  74. package/dist/services/platform-service.d.ts.map +1 -0
  75. package/dist/services/platform-service.js +10 -0
  76. package/dist/services/service-container.d.ts +25 -0
  77. package/dist/services/service-container.d.ts.map +1 -0
  78. package/dist/services/service-container.js +1 -0
  79. package/dist/services/storage-service.d.ts +26 -0
  80. package/dist/services/storage-service.d.ts.map +1 -0
  81. package/dist/services/storage-service.js +1 -0
  82. package/dist/services/websocket-service.d.ts +36 -0
  83. package/dist/services/websocket-service.d.ts.map +1 -0
  84. package/dist/services/websocket-service.js +4 -0
  85. package/dist/smart-chunker.d.ts +72 -0
  86. package/dist/smart-chunker.d.ts.map +1 -0
  87. package/dist/smart-chunker.js +317 -0
  88. package/dist/storage/client-storage.d.ts +148 -0
  89. package/dist/storage/client-storage.d.ts.map +1 -0
  90. package/dist/storage/client-storage.js +62 -0
  91. package/dist/storage/in-memory-storage-service.d.ts +7 -0
  92. package/dist/storage/in-memory-storage-service.d.ts.map +1 -0
  93. package/dist/storage/in-memory-storage-service.js +24 -0
  94. package/dist/storage/index.d.ts +3 -0
  95. package/dist/storage/index.d.ts.map +1 -0
  96. package/dist/storage/index.js +2 -0
  97. package/dist/types/buffered-chunk.d.ts +6 -0
  98. package/dist/types/buffered-chunk.d.ts.map +1 -0
  99. package/dist/types/buffered-chunk.js +1 -0
  100. package/dist/types/chunk-metrics.d.ts +12 -0
  101. package/dist/types/chunk-metrics.d.ts.map +1 -0
  102. package/dist/types/chunk-metrics.js +1 -0
  103. package/dist/types/flow-result.d.ts +11 -0
  104. package/dist/types/flow-result.d.ts.map +1 -0
  105. package/dist/types/flow-result.js +1 -0
  106. package/dist/types/flow-upload-config.d.ts +54 -0
  107. package/dist/types/flow-upload-config.d.ts.map +1 -0
  108. package/dist/types/flow-upload-config.js +1 -0
  109. package/dist/types/flow-upload-item.d.ts +16 -0
  110. package/dist/types/flow-upload-item.d.ts.map +1 -0
  111. package/dist/types/flow-upload-item.js +1 -0
  112. package/dist/types/flow-upload-options.d.ts +41 -0
  113. package/dist/types/flow-upload-options.d.ts.map +1 -0
  114. package/dist/types/flow-upload-options.js +1 -0
  115. package/dist/types/index.d.ts +14 -0
  116. package/dist/types/index.d.ts.map +1 -0
  117. package/dist/types/index.js +13 -0
  118. package/dist/types/multi-flow-upload-options.d.ts +33 -0
  119. package/dist/types/multi-flow-upload-options.d.ts.map +1 -0
  120. package/dist/types/multi-flow-upload-options.js +1 -0
  121. package/dist/types/multi-flow-upload-state.d.ts +9 -0
  122. package/dist/types/multi-flow-upload-state.d.ts.map +1 -0
  123. package/dist/types/multi-flow-upload-state.js +1 -0
  124. package/dist/types/performance-insights.d.ts +11 -0
  125. package/dist/types/performance-insights.d.ts.map +1 -0
  126. package/dist/types/performance-insights.js +1 -0
  127. package/dist/types/previous-upload.d.ts +20 -0
  128. package/dist/types/previous-upload.d.ts.map +1 -0
  129. package/dist/types/previous-upload.js +9 -0
  130. package/dist/types/upload-options.d.ts +40 -0
  131. package/dist/types/upload-options.d.ts.map +1 -0
  132. package/dist/types/upload-options.js +1 -0
  133. package/dist/types/upload-response.d.ts +6 -0
  134. package/dist/types/upload-response.d.ts.map +1 -0
  135. package/dist/types/upload-response.js +1 -0
  136. package/dist/types/upload-result.d.ts +57 -0
  137. package/dist/types/upload-result.d.ts.map +1 -0
  138. package/dist/types/upload-result.js +1 -0
  139. package/dist/types/upload-session-metrics.d.ts +16 -0
  140. package/dist/types/upload-session-metrics.d.ts.map +1 -0
  141. package/dist/types/upload-session-metrics.js +1 -0
  142. package/dist/upload/chunk-upload.d.ts +40 -0
  143. package/dist/upload/chunk-upload.d.ts.map +1 -0
  144. package/dist/upload/chunk-upload.js +82 -0
  145. package/dist/upload/flow-upload.d.ts +48 -0
  146. package/dist/upload/flow-upload.d.ts.map +1 -0
  147. package/dist/upload/flow-upload.js +240 -0
  148. package/dist/upload/index.d.ts +3 -0
  149. package/dist/upload/index.d.ts.map +1 -0
  150. package/dist/upload/index.js +2 -0
  151. package/dist/upload/parallel-upload.d.ts +65 -0
  152. package/dist/upload/parallel-upload.d.ts.map +1 -0
  153. package/dist/upload/parallel-upload.js +231 -0
  154. package/dist/upload/single-upload.d.ts +118 -0
  155. package/dist/upload/single-upload.d.ts.map +1 -0
  156. package/dist/upload/single-upload.js +332 -0
  157. package/dist/upload/upload-manager.d.ts +30 -0
  158. package/dist/upload/upload-manager.d.ts.map +1 -0
  159. package/dist/upload/upload-manager.js +57 -0
  160. package/dist/upload/upload-metrics.d.ts +37 -0
  161. package/dist/upload/upload-metrics.d.ts.map +1 -0
  162. package/dist/upload/upload-metrics.js +236 -0
  163. package/dist/upload/upload-storage.d.ts +32 -0
  164. package/dist/upload/upload-storage.d.ts.map +1 -0
  165. package/dist/upload/upload-storage.js +46 -0
  166. package/dist/upload/upload-strategy.d.ts +66 -0
  167. package/dist/upload/upload-strategy.d.ts.map +1 -0
  168. package/dist/upload/upload-strategy.js +171 -0
  169. package/dist/upload/upload-utils.d.ts +26 -0
  170. package/dist/upload/upload-utils.d.ts.map +1 -0
  171. package/dist/upload/upload-utils.js +80 -0
  172. package/package.json +29 -0
  173. package/src/__tests__/smart-chunking.test.ts +399 -0
  174. package/src/auth/__tests__/auth-http-client.test.ts +327 -0
  175. package/src/auth/__tests__/direct-auth.test.ts +135 -0
  176. package/src/auth/__tests__/no-auth.test.ts +40 -0
  177. package/src/auth/__tests__/saas-auth.test.ts +337 -0
  178. package/src/auth/auth-http-client.ts +150 -0
  179. package/src/auth/direct-auth.ts +121 -0
  180. package/src/auth/index.ts +5 -0
  181. package/src/auth/no-auth.ts +39 -0
  182. package/src/auth/saas-auth.ts +218 -0
  183. package/src/auth/types.ts +105 -0
  184. package/src/chunk-buffer.ts +287 -0
  185. package/src/client/create-uploadista-client.ts +901 -0
  186. package/src/client/index.ts +3 -0
  187. package/src/client/uploadista-api.ts +857 -0
  188. package/src/client/uploadista-websocket-manager.ts +275 -0
  189. package/src/error.ts +149 -0
  190. package/src/index.ts +13 -0
  191. package/src/logger.ts +104 -0
  192. package/src/mock-data-store.ts +97 -0
  193. package/src/network-monitor.ts +445 -0
  194. package/src/services/abort-controller-service.ts +21 -0
  195. package/src/services/checksum-service.ts +3 -0
  196. package/src/services/file-reader-service.ts +44 -0
  197. package/src/services/fingerprint-service.ts +6 -0
  198. package/src/services/http-client.ts +229 -0
  199. package/src/services/id-generation-service.ts +9 -0
  200. package/src/services/index.ts +10 -0
  201. package/src/services/platform-service.ts +65 -0
  202. package/src/services/service-container.ts +24 -0
  203. package/src/services/storage-service.ts +29 -0
  204. package/src/services/websocket-service.ts +33 -0
  205. package/src/smart-chunker.ts +451 -0
  206. package/src/storage/client-storage.ts +186 -0
  207. package/src/storage/in-memory-storage-service.ts +33 -0
  208. package/src/storage/index.ts +2 -0
  209. package/src/types/buffered-chunk.ts +5 -0
  210. package/src/types/chunk-metrics.ts +11 -0
  211. package/src/types/flow-result.ts +14 -0
  212. package/src/types/flow-upload-config.ts +56 -0
  213. package/src/types/flow-upload-item.ts +16 -0
  214. package/src/types/flow-upload-options.ts +56 -0
  215. package/src/types/index.ts +13 -0
  216. package/src/types/multi-flow-upload-options.ts +39 -0
  217. package/src/types/multi-flow-upload-state.ts +9 -0
  218. package/src/types/performance-insights.ts +7 -0
  219. package/src/types/previous-upload.ts +22 -0
  220. package/src/types/upload-options.ts +56 -0
  221. package/src/types/upload-response.ts +6 -0
  222. package/src/types/upload-result.ts +60 -0
  223. package/src/types/upload-session-metrics.ts +15 -0
  224. package/src/upload/chunk-upload.ts +151 -0
  225. package/src/upload/flow-upload.ts +367 -0
  226. package/src/upload/index.ts +2 -0
  227. package/src/upload/parallel-upload.ts +387 -0
  228. package/src/upload/single-upload.ts +554 -0
  229. package/src/upload/upload-manager.ts +106 -0
  230. package/src/upload/upload-metrics.ts +340 -0
  231. package/src/upload/upload-storage.ts +87 -0
  232. package/src/upload/upload-strategy.ts +296 -0
  233. package/src/upload/upload-utils.ts +114 -0
  234. package/tsconfig.json +23 -0
  235. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,337 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { SaasAuthManager } from "../saas-auth";
3
+ import type { SaasAuthConfig } from "../types";
4
+
5
+ // Mock fetch globally
6
+ global.fetch = vi.fn();
7
+
8
+ describe("SaasAuthManager", () => {
9
+ beforeEach(() => {
10
+ vi.clearAllMocks();
11
+ });
12
+
13
+ describe("fetchToken", () => {
14
+ it("should fetch token from auth server", async () => {
15
+ const config: SaasAuthConfig = {
16
+ mode: "saas",
17
+ authServerUrl: "https://auth.example.com/token",
18
+ getCredentials: () => ({
19
+ username: "user",
20
+ password: "pass",
21
+ }),
22
+ };
23
+
24
+ vi.mocked(global.fetch).mockResolvedValueOnce({
25
+ ok: true,
26
+ json: async () => ({
27
+ token: "jwt-token-123",
28
+ expiresIn: 3600,
29
+ }),
30
+ });
31
+
32
+ const manager = new SaasAuthManager(config);
33
+ const result = await manager.fetchToken();
34
+
35
+ expect(result).toEqual({
36
+ token: "jwt-token-123",
37
+ expiresIn: 3600,
38
+ });
39
+
40
+ expect(global.fetch).toHaveBeenCalledWith(
41
+ "https://auth.example.com/token",
42
+ {
43
+ method: "POST",
44
+ headers: {
45
+ "Content-Type": "application/json",
46
+ },
47
+ body: JSON.stringify({ username: "user", password: "pass" }),
48
+ },
49
+ );
50
+ });
51
+
52
+ it("should handle auth server errors", async () => {
53
+ const config: SaasAuthConfig = {
54
+ mode: "saas",
55
+ authServerUrl: "https://auth.example.com/token",
56
+ getCredentials: () => ({ username: "user", password: "wrong" }),
57
+ };
58
+
59
+ vi.mocked(global.fetch).mockResolvedValueOnce({
60
+ ok: false,
61
+ status: 401,
62
+ text: async () => JSON.stringify({ error: "Invalid credentials" }),
63
+ });
64
+
65
+ const manager = new SaasAuthManager(config);
66
+
67
+ await expect(manager.fetchToken()).rejects.toThrow(
68
+ "Failed to fetch auth token: Invalid credentials",
69
+ );
70
+ });
71
+
72
+ it("should handle network errors", async () => {
73
+ const config: SaasAuthConfig = {
74
+ mode: "saas",
75
+ authServerUrl: "https://auth.example.com/token",
76
+ getCredentials: () => ({ username: "user", password: "pass" }),
77
+ };
78
+
79
+ vi.mocked(global.fetch).mockRejectedValueOnce(new Error("Network error"));
80
+
81
+ const manager = new SaasAuthManager(config);
82
+
83
+ await expect(manager.fetchToken()).rejects.toThrow(
84
+ "Failed to fetch auth token: Network error",
85
+ );
86
+ });
87
+
88
+ it("should validate token response format", async () => {
89
+ const config: SaasAuthConfig = {
90
+ mode: "saas",
91
+ authServerUrl: "https://auth.example.com/token",
92
+ getCredentials: () => ({ username: "user", password: "pass" }),
93
+ };
94
+
95
+ vi.mocked(global.fetch).mockResolvedValueOnce({
96
+ ok: true,
97
+ json: async () => ({ noToken: "here" }), // Missing token field
98
+ });
99
+
100
+ const manager = new SaasAuthManager(config);
101
+
102
+ await expect(manager.fetchToken()).rejects.toThrow(
103
+ "Auth server response missing 'token' field",
104
+ );
105
+ });
106
+ });
107
+
108
+ describe("attachToken", () => {
109
+ it("should attach token as Bearer header", async () => {
110
+ const config: SaasAuthConfig = {
111
+ mode: "saas",
112
+ authServerUrl: "https://auth.example.com/token",
113
+ getCredentials: () => ({ username: "user", password: "pass" }),
114
+ };
115
+
116
+ vi.mocked(global.fetch).mockResolvedValueOnce({
117
+ ok: true,
118
+ json: async () => ({ token: "jwt-token-123" }),
119
+ });
120
+
121
+ const manager = new SaasAuthManager(config);
122
+ const result = await manager.attachToken({
123
+ "Content-Type": "application/json",
124
+ });
125
+
126
+ expect(result).toEqual({
127
+ "Content-Type": "application/json",
128
+ Authorization: "Bearer jwt-token-123",
129
+ });
130
+ });
131
+
132
+ it("should cache token and reuse it", async () => {
133
+ const config: SaasAuthConfig = {
134
+ mode: "saas",
135
+ authServerUrl: "https://auth.example.com/token",
136
+ getCredentials: () => ({ username: "user", password: "pass" }),
137
+ };
138
+
139
+ vi.mocked(global.fetch).mockResolvedValueOnce({
140
+ ok: true,
141
+ json: async () => ({ token: "jwt-token-123" }),
142
+ });
143
+
144
+ const manager = new SaasAuthManager(config);
145
+
146
+ // First call - should fetch token
147
+ await manager.attachToken();
148
+ expect(global.fetch).toHaveBeenCalledTimes(1);
149
+
150
+ // Second call - should use cached token
151
+ await manager.attachToken();
152
+ expect(global.fetch).toHaveBeenCalledTimes(1); // Still 1, not 2
153
+ });
154
+
155
+ it("should cache tokens per job ID", async () => {
156
+ const config: SaasAuthConfig = {
157
+ mode: "saas",
158
+ authServerUrl: "https://auth.example.com/token",
159
+ getCredentials: () => ({ username: "user", password: "pass" }),
160
+ };
161
+
162
+ vi.mocked(global.fetch)
163
+ .mockResolvedValueOnce({
164
+ ok: true,
165
+ json: async () => ({ token: "jwt-token-1" }),
166
+ })
167
+ .mockResolvedValueOnce({
168
+ ok: true,
169
+ json: async () => ({ token: "jwt-token-2" }),
170
+ });
171
+
172
+ const manager = new SaasAuthManager(config);
173
+
174
+ // Fetch token for job 1
175
+ const result1 = await manager.attachToken({}, "job-1");
176
+ expect(result1.Authorization).toBe("Bearer jwt-token-1");
177
+
178
+ // Fetch token for job 2
179
+ const result2 = await manager.attachToken({}, "job-2");
180
+ expect(result2.Authorization).toBe("Bearer jwt-token-2");
181
+
182
+ // Reuse token for job 1
183
+ const result3 = await manager.attachToken({}, "job-1");
184
+ expect(result3.Authorization).toBe("Bearer jwt-token-1");
185
+
186
+ // Should have fetched twice (once per job)
187
+ expect(global.fetch).toHaveBeenCalledTimes(2);
188
+ });
189
+
190
+ it("should refetch expired tokens", async () => {
191
+ const config: SaasAuthConfig = {
192
+ mode: "saas",
193
+ authServerUrl: "https://auth.example.com/token",
194
+ getCredentials: () => ({ username: "user", password: "pass" }),
195
+ };
196
+
197
+ // First token expires in 1 second
198
+ vi.mocked(global.fetch)
199
+ .mockResolvedValueOnce({
200
+ ok: true,
201
+ json: async () => ({
202
+ token: "jwt-token-old",
203
+ expiresIn: 0.001, // Expires very soon
204
+ }),
205
+ })
206
+ .mockResolvedValueOnce({
207
+ ok: true,
208
+ json: async () => ({ token: "jwt-token-new" }),
209
+ });
210
+
211
+ const manager = new SaasAuthManager(config);
212
+
213
+ // First call - fetch initial token
214
+ await manager.attachToken();
215
+
216
+ // Wait for token to expire
217
+ await new Promise((resolve) => setTimeout(resolve, 100));
218
+
219
+ // Second call - should fetch new token because old one expired
220
+ const result = await manager.attachToken();
221
+ expect(result.Authorization).toBe("Bearer jwt-token-new");
222
+ expect(global.fetch).toHaveBeenCalledTimes(2);
223
+ });
224
+ });
225
+
226
+ describe("clearToken", () => {
227
+ it("should clear cached token for specific job", async () => {
228
+ const config: SaasAuthConfig = {
229
+ mode: "saas",
230
+ authServerUrl: "https://auth.example.com/token",
231
+ getCredentials: () => ({ username: "user", password: "pass" }),
232
+ };
233
+
234
+ vi.mocked(global.fetch)
235
+ .mockResolvedValueOnce({
236
+ ok: true,
237
+ json: async () => ({ token: "jwt-token-1" }),
238
+ })
239
+ .mockResolvedValueOnce({
240
+ ok: true,
241
+ json: async () => ({ token: "jwt-token-2" }),
242
+ });
243
+
244
+ const manager = new SaasAuthManager(config);
245
+
246
+ // Cache token for job
247
+ await manager.attachToken({}, "job-1");
248
+ expect(global.fetch).toHaveBeenCalledTimes(1);
249
+
250
+ // Clear token
251
+ manager.clearToken("job-1");
252
+
253
+ // Next call should fetch new token
254
+ await manager.attachToken({}, "job-1");
255
+ expect(global.fetch).toHaveBeenCalledTimes(2);
256
+ });
257
+ });
258
+
259
+ describe("clearAllTokens", () => {
260
+ it("should clear all cached tokens", async () => {
261
+ const config: SaasAuthConfig = {
262
+ mode: "saas",
263
+ authServerUrl: "https://auth.example.com/token",
264
+ getCredentials: () => ({ username: "user", password: "pass" }),
265
+ };
266
+
267
+ vi.mocked(global.fetch)
268
+ .mockResolvedValueOnce({
269
+ ok: true,
270
+ json: async () => ({ token: "jwt-token-1" }),
271
+ })
272
+ .mockResolvedValueOnce({
273
+ ok: true,
274
+ json: async () => ({ token: "jwt-token-2" }),
275
+ })
276
+ .mockResolvedValueOnce({
277
+ ok: true,
278
+ json: async () => ({ token: "jwt-token-3" }),
279
+ });
280
+
281
+ const manager = new SaasAuthManager(config);
282
+
283
+ // Cache tokens for multiple jobs
284
+ await manager.attachToken({}, "job-1");
285
+ await manager.attachToken(); // Global token
286
+
287
+ // Clear all
288
+ manager.clearAllTokens();
289
+
290
+ // Next calls should fetch new tokens
291
+ await manager.attachToken();
292
+ expect(global.fetch).toHaveBeenCalledTimes(3);
293
+ });
294
+ });
295
+
296
+ describe("getCacheStats", () => {
297
+ it("should return cache statistics", async () => {
298
+ const config: SaasAuthConfig = {
299
+ mode: "saas",
300
+ authServerUrl: "https://auth.example.com/token",
301
+ getCredentials: () => ({ username: "user", password: "pass" }),
302
+ };
303
+
304
+ vi.mocked(global.fetch)
305
+ .mockResolvedValueOnce({
306
+ ok: true,
307
+ json: async () => ({ token: "jwt-token-1" }),
308
+ })
309
+ .mockResolvedValueOnce({
310
+ ok: true,
311
+ json: async () => ({ token: "jwt-token-2" }),
312
+ })
313
+ .mockResolvedValueOnce({
314
+ ok: true,
315
+ json: async () => ({ token: "jwt-token-3" }),
316
+ });
317
+
318
+ const manager = new SaasAuthManager(config);
319
+
320
+ // Initially empty
321
+ expect(manager.getCacheStats()).toEqual({
322
+ cachedJobCount: 0,
323
+ hasGlobalToken: false,
324
+ });
325
+
326
+ // Cache tokens
327
+ await manager.attachToken({}, "job-1");
328
+ await manager.attachToken({}, "job-2");
329
+ await manager.attachToken(); // Global
330
+
331
+ expect(manager.getCacheStats()).toEqual({
332
+ cachedJobCount: 2,
333
+ hasGlobalToken: true,
334
+ });
335
+ });
336
+ });
337
+ });
@@ -0,0 +1,150 @@
1
+ import type {
2
+ HttpClient,
3
+ HttpRequestOptions,
4
+ HttpResponse,
5
+ } from "../services/http-client";
6
+ import type { DirectAuthManager } from "./direct-auth";
7
+ import type { NoAuthManager } from "./no-auth";
8
+ import type { SaasAuthManager } from "./saas-auth";
9
+
10
+ /**
11
+ * Union type of all auth managers
12
+ */
13
+ export type AuthManager = DirectAuthManager | SaasAuthManager | NoAuthManager;
14
+
15
+ /**
16
+ * Auth-aware HTTP client wrapper.
17
+ *
18
+ * Wraps a standard HttpClient and automatically attaches authentication
19
+ * credentials/tokens to all HTTP requests based on the configured auth manager.
20
+ *
21
+ * The wrapper delegates all non-auth concerns (connection pooling, metrics, etc.)
22
+ * to the underlying HttpClient and only adds the auth layer on top.
23
+ */
24
+ export class AuthHttpClient implements HttpClient {
25
+ constructor(
26
+ private httpClient: HttpClient,
27
+ private authManager: AuthManager,
28
+ ) {}
29
+
30
+ /**
31
+ * Make an HTTP request with authentication credentials attached.
32
+ * Calls the auth manager to attach credentials before delegating to the underlying client.
33
+ */
34
+ async request(
35
+ url: string,
36
+ options: HttpRequestOptions = {},
37
+ ): Promise<HttpResponse> {
38
+ try {
39
+ // Attach auth credentials to request headers
40
+ const authenticatedHeaders = await this.attachAuthCredentials(
41
+ options.headers || {},
42
+ url,
43
+ );
44
+
45
+ // Delegate to underlying HTTP client with authenticated headers
46
+ return await this.httpClient.request(url, {
47
+ ...options,
48
+ headers: authenticatedHeaders,
49
+ // include credentials for cors if needed
50
+ credentials:
51
+ this.authManager.getType() === "no-auth" ||
52
+ this.authManager.getType() === "saas"
53
+ ? "omit"
54
+ : (options.credentials ?? "include"),
55
+ });
56
+ } catch (error) {
57
+ // If auth fails, wrap error with context
58
+ if (error instanceof Error && error.message.includes("auth")) {
59
+ throw error; // Re-throw auth errors as-is
60
+ }
61
+
62
+ // For other errors, let them propagate
63
+ throw error;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Attach authentication credentials to request headers.
69
+ * Delegates to the appropriate auth manager method.
70
+ */
71
+ private async attachAuthCredentials(
72
+ headers: Record<string, string>,
73
+ url: string,
74
+ ): Promise<Record<string, string>> {
75
+ // Check if this is a DirectAuthManager or SaasAuthManager
76
+ if ("attachCredentials" in this.authManager) {
77
+ // DirectAuthManager or NoAuthManager
78
+ return await this.authManager.attachCredentials(headers);
79
+ }
80
+
81
+ if ("attachToken" in this.authManager) {
82
+ // SaasAuthManager - extract job ID from URL if present
83
+ const jobId = this.extractJobIdFromUrl(url);
84
+ return await this.authManager.attachToken(headers, jobId);
85
+ }
86
+
87
+ // Fallback - return headers unchanged
88
+ return headers;
89
+ }
90
+
91
+ /**
92
+ * Extract job ID from URL for SaaS mode token caching.
93
+ * Looks for patterns like /upload/{id} or /jobs/{id} in the URL.
94
+ */
95
+ private extractJobIdFromUrl(url: string): string | undefined {
96
+ // Match patterns like:
97
+ // - /api/upload/{uploadId}
98
+ // - /api/flow/{flowId}/{storageId}
99
+ // - /api/jobs/{jobId}/status
100
+ // - /api/jobs/{jobId}/continue/{nodeId}
101
+
102
+ const uploadMatch = url.match(/\/api\/upload\/([^/?]+)/);
103
+ if (uploadMatch) {
104
+ return uploadMatch[1];
105
+ }
106
+
107
+ const flowMatch = url.match(/\/api\/flow\/([^/?]+)/);
108
+ if (flowMatch) {
109
+ return flowMatch[1];
110
+ }
111
+
112
+ const jobMatch = url.match(/\/api\/jobs\/([^/?]+)/);
113
+ if (jobMatch) {
114
+ return jobMatch[1];
115
+ }
116
+
117
+ // No job ID found - SaaS mode will use global token
118
+ return undefined;
119
+ }
120
+
121
+ /**
122
+ * Delegate metrics methods to underlying HTTP client
123
+ */
124
+ getMetrics() {
125
+ return this.httpClient.getMetrics();
126
+ }
127
+
128
+ getDetailedMetrics() {
129
+ return this.httpClient.getDetailedMetrics();
130
+ }
131
+
132
+ reset() {
133
+ this.httpClient.reset();
134
+ }
135
+
136
+ async close() {
137
+ await this.httpClient.close();
138
+ }
139
+
140
+ async warmupConnections(urls: string[]) {
141
+ await this.httpClient.warmupConnections(urls);
142
+ }
143
+
144
+ /**
145
+ * Get the underlying auth manager for advanced use cases
146
+ */
147
+ getAuthManager(): AuthManager {
148
+ return this.authManager;
149
+ }
150
+ }
@@ -0,0 +1,121 @@
1
+ import type { Logger } from "../logger";
2
+ import type { PlatformService } from "../services/platform-service";
3
+ import type { DirectAuthConfig } from "./types";
4
+ import { BaseAuthManager } from "./types";
5
+
6
+ /**
7
+ * Direct auth manager - handles credential attachment for "bring your own auth" mode.
8
+ *
9
+ * This manager calls the user-provided getCredentials() function before each request
10
+ * and attaches the returned credentials (headers, cookies) to the HTTP request.
11
+ *
12
+ * Supports any authentication protocol: OAuth, JWT, API keys, session cookies, etc.
13
+ */
14
+ export class DirectAuthManager extends BaseAuthManager {
15
+ constructor(
16
+ private config: DirectAuthConfig,
17
+ private platformService: PlatformService,
18
+ private logger: Logger,
19
+ ) {
20
+ super("direct");
21
+ }
22
+
23
+ /**
24
+ * Attach credentials to an HTTP request by calling getCredentials() and
25
+ * merging the returned headers/cookies with the request.
26
+ *
27
+ * @param headers - Existing request headers
28
+ * @returns Updated headers with credentials attached
29
+ * @throws Error if getCredentials() throws or returns invalid data
30
+ */
31
+ async attachCredentials(
32
+ headers: Record<string, string> = {},
33
+ ): Promise<Record<string, string>> {
34
+ try {
35
+ if (!this.config.getCredentials) {
36
+ return headers;
37
+ }
38
+
39
+ // Call user's credential provider (may be async)
40
+ const credentials = await Promise.resolve(this.config.getCredentials());
41
+
42
+ // Validate credentials
43
+ if (!credentials || typeof credentials !== "object") {
44
+ throw new Error(
45
+ "getCredentials() must return an object with headers and/or cookies",
46
+ );
47
+ }
48
+
49
+ // Merge credential headers with existing headers
50
+ const updatedHeaders = { ...headers };
51
+
52
+ if (credentials.headers) {
53
+ this.validateHeaders(credentials.headers);
54
+ Object.assign(updatedHeaders, credentials.headers);
55
+ }
56
+
57
+ // Note: Cookie handling would be browser-specific
58
+ // For now, we only support headers as cookies are automatically
59
+ // handled by the browser when using fetch()
60
+ if (credentials.cookies) {
61
+ this.attachCookies(updatedHeaders, credentials.cookies);
62
+ }
63
+
64
+ return updatedHeaders;
65
+ } catch (error) {
66
+ // Wrap errors with context
67
+ const message = error instanceof Error ? error.message : String(error);
68
+ throw new Error(`Failed to attach auth credentials: ${message}`);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Validate that headers is a valid object with string keys and values
74
+ */
75
+ private validateHeaders(headers: Record<string, string>): void {
76
+ if (typeof headers !== "object" || headers === null) {
77
+ throw new Error("headers must be an object");
78
+ }
79
+
80
+ for (const [key, value] of Object.entries(headers)) {
81
+ if (typeof key !== "string" || typeof value !== "string") {
82
+ throw new Error(
83
+ `Invalid header: key and value must be strings (got ${key}: ${typeof value})`,
84
+ );
85
+ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Attach cookies to request headers.
91
+ * In browser environments, cookies are automatically handled by fetch().
92
+ * In Node.js, we need to manually add them to the Cookie header.
93
+ */
94
+ private attachCookies(
95
+ headers: Record<string, string>,
96
+ cookies: Record<string, string>,
97
+ ): void {
98
+ // Check if we're in a browser environment
99
+ const isBrowser = this.platformService.isBrowser();
100
+
101
+ if (isBrowser) {
102
+ // In browsers, fetch() automatically sends cookies for same-origin requests
103
+ // For cross-origin, the server needs to set CORS headers and credentials: 'include'
104
+ // We can't manually set cookies in headers for security reasons
105
+ // So we just warn if cookies are provided in direct mode
106
+ this.logger.warn(
107
+ "DirectAuth: Cookies are automatically handled by the browser. " +
108
+ "Ensure your server has proper CORS configuration with credentials support.",
109
+ );
110
+ } else {
111
+ // In Node.js, we can manually build the Cookie header
112
+ const cookieString = Object.entries(cookies)
113
+ .map(([key, value]) => `${key}=${value}`)
114
+ .join("; ");
115
+
116
+ if (cookieString) {
117
+ headers.Cookie = cookieString;
118
+ }
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./auth-http-client";
2
+ export * from "./direct-auth";
3
+ export * from "./no-auth";
4
+ export * from "./saas-auth";
5
+ export * from "./types";
@@ -0,0 +1,39 @@
1
+ import { BaseAuthManager } from "./types";
2
+
3
+ /**
4
+ * No-auth manager - pass-through implementation for backward compatibility.
5
+ *
6
+ * When no auth configuration is provided, this manager is used to maintain
7
+ * a consistent interface without adding any authentication to requests.
8
+ */
9
+ export class NoAuthManager extends BaseAuthManager {
10
+ constructor() {
11
+ super("no-auth");
12
+ }
13
+
14
+ /**
15
+ * Pass through headers without modification.
16
+ *
17
+ * @param headers - Existing request headers
18
+ * @returns Same headers unchanged
19
+ */
20
+ async attachCredentials(
21
+ headers: Record<string, string> = {},
22
+ ): Promise<Record<string, string>> {
23
+ return headers;
24
+ }
25
+
26
+ /**
27
+ * No-op for clearing tokens (NoAuthManager doesn't cache anything)
28
+ */
29
+ clearToken(_jobId: string): void {
30
+ // No-op
31
+ }
32
+
33
+ /**
34
+ * No-op for clearing all tokens
35
+ */
36
+ clearAllTokens(): void {
37
+ // No-op
38
+ }
39
+ }