@inneropen/marvin-sdk 2.0.1 → 3.0.1
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.
- package/README.md +179 -14
- package/dist/{HttpClient-CLtgQGXH.d.cts → HttpClient-CdF3IHM1.d.cts} +49 -2
- package/dist/{HttpClient-CLtgQGXH.d.ts → HttpClient-CdF3IHM1.d.ts} +49 -2
- package/dist/chunk-R3EZI43S.js +855 -0
- package/dist/chunk-WJC4X7TB.js +738 -0
- package/dist/index-D5zkKa4N.d.cts +3394 -0
- package/dist/index-D5zkKa4N.d.ts +3394 -0
- package/dist/index.cjs +599 -213
- package/dist/index.d.cts +20 -544
- package/dist/index.d.ts +20 -544
- package/dist/index.js +27 -791
- package/dist/marvin-B_gJRxHK.d.cts +611 -0
- package/dist/marvin-C5tRQ2nD.d.ts +611 -0
- package/dist/platform.cjs +512 -69
- package/dist/platform.d.cts +141 -3381
- package/dist/platform.d.ts +141 -3381
- package/dist/platform.js +283 -59
- package/dist/publish.cjs +1355 -0
- package/dist/publish.d.cts +49 -0
- package/dist/publish.d.ts +49 -0
- package/dist/publish.js +75 -0
- package/dist/types/index.d.cts +1 -168
- package/dist/types/index.d.ts +1 -168
- package/package.json +11 -6
- package/dist/chunk-25F3IF6S.js +0 -396
package/dist/platform.cjs
CHANGED
|
@@ -43,7 +43,8 @@ __export(platform_exports, {
|
|
|
43
43
|
WebhooksModule: () => WebhooksModule,
|
|
44
44
|
WorkspaceMembersModule: () => WorkspaceMembersModule,
|
|
45
45
|
WorkspacesModule: () => WorkspacesModule,
|
|
46
|
-
createPlatformClient: () => createPlatformClient
|
|
46
|
+
createPlatformClient: () => createPlatformClient,
|
|
47
|
+
createPlatformConfigFromEnv: () => createPlatformConfigFromEnv
|
|
47
48
|
});
|
|
48
49
|
module.exports = __toCommonJS(platform_exports);
|
|
49
50
|
|
|
@@ -77,6 +78,49 @@ ${body}` : ""}`;
|
|
|
77
78
|
return new _MarvinApiError(message, status, endpoint, body);
|
|
78
79
|
}
|
|
79
80
|
};
|
|
81
|
+
var MarvinAuthError = class _MarvinAuthError extends MarvinError {
|
|
82
|
+
constructor(message, statusCode) {
|
|
83
|
+
super(message, "AUTH_ERROR", statusCode);
|
|
84
|
+
this.name = "MarvinAuthError";
|
|
85
|
+
Object.setPrototypeOf(this, _MarvinAuthError.prototype);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var MarvinValidationError = class _MarvinValidationError extends MarvinError {
|
|
89
|
+
constructor(message, field) {
|
|
90
|
+
super(message, "VALIDATION_ERROR");
|
|
91
|
+
this.field = field;
|
|
92
|
+
this.name = "MarvinValidationError";
|
|
93
|
+
Object.setPrototypeOf(this, _MarvinValidationError.prototype);
|
|
94
|
+
}
|
|
95
|
+
field;
|
|
96
|
+
};
|
|
97
|
+
var MarvinNotFoundError = class _MarvinNotFoundError extends MarvinError {
|
|
98
|
+
constructor(message, endpoint) {
|
|
99
|
+
super(message, "NOT_FOUND", 404);
|
|
100
|
+
this.endpoint = endpoint;
|
|
101
|
+
this.name = "MarvinNotFoundError";
|
|
102
|
+
Object.setPrototypeOf(this, _MarvinNotFoundError.prototype);
|
|
103
|
+
}
|
|
104
|
+
endpoint;
|
|
105
|
+
};
|
|
106
|
+
var MarvinNetworkError = class _MarvinNetworkError extends MarvinError {
|
|
107
|
+
constructor(message, originalError) {
|
|
108
|
+
super(message, "NETWORK_ERROR");
|
|
109
|
+
this.originalError = originalError;
|
|
110
|
+
this.name = "MarvinNetworkError";
|
|
111
|
+
Object.setPrototypeOf(this, _MarvinNetworkError.prototype);
|
|
112
|
+
}
|
|
113
|
+
originalError;
|
|
114
|
+
};
|
|
115
|
+
var MarvinServerError = class _MarvinServerError extends MarvinError {
|
|
116
|
+
constructor(message, statusCode, endpoint) {
|
|
117
|
+
super(message, "SERVER_ERROR", statusCode);
|
|
118
|
+
this.endpoint = endpoint;
|
|
119
|
+
this.name = "MarvinServerError";
|
|
120
|
+
Object.setPrototypeOf(this, _MarvinServerError.prototype);
|
|
121
|
+
}
|
|
122
|
+
endpoint;
|
|
123
|
+
};
|
|
80
124
|
|
|
81
125
|
// src/core/http/HttpClient.ts
|
|
82
126
|
var HttpClient = class {
|
|
@@ -85,12 +129,47 @@ var HttpClient = class {
|
|
|
85
129
|
defaultHeaders;
|
|
86
130
|
credentials;
|
|
87
131
|
timeout;
|
|
132
|
+
debug;
|
|
133
|
+
retryConfig;
|
|
134
|
+
logger;
|
|
88
135
|
constructor(config) {
|
|
89
136
|
this.baseUrl = config.baseUrl;
|
|
90
137
|
this.auth = config.auth;
|
|
91
138
|
this.defaultHeaders = config.defaultHeaders || {};
|
|
92
139
|
this.credentials = config.credentials || "same-origin";
|
|
93
|
-
|
|
140
|
+
const MAX_TIMEOUT = 12e4;
|
|
141
|
+
this.timeout = Math.min(config.timeout || 3e4, MAX_TIMEOUT);
|
|
142
|
+
this.debug = config.debug ?? false;
|
|
143
|
+
this.logger = config.logger ?? console;
|
|
144
|
+
this.retryConfig = {
|
|
145
|
+
maxRetries: config.retry?.maxRetries ?? 3,
|
|
146
|
+
initialDelay: config.retry?.initialDelay ?? 1e3,
|
|
147
|
+
maxDelay: config.retry?.maxDelay ?? 1e4,
|
|
148
|
+
retryableStatuses: config.retry?.retryableStatuses ?? [408, 429, 500, 502, 503, 504]
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Validate path parameter to prevent path traversal attacks
|
|
153
|
+
*/
|
|
154
|
+
validatePathParam(param, paramName = "parameter") {
|
|
155
|
+
if (typeof param === "number") {
|
|
156
|
+
return String(param);
|
|
157
|
+
}
|
|
158
|
+
if (!param || typeof param !== "string") {
|
|
159
|
+
throw new MarvinValidationError(`Invalid ${paramName}: must be a non-empty string or number`);
|
|
160
|
+
}
|
|
161
|
+
if (param.includes("..") || param.includes("/") || param.includes("\\")) {
|
|
162
|
+
throw new MarvinValidationError(
|
|
163
|
+
`Invalid ${paramName}: cannot contain path traversal characters (.. / \\)`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
if (param.includes("\0")) {
|
|
167
|
+
throw new MarvinValidationError(`Invalid ${paramName}: cannot contain null bytes`);
|
|
168
|
+
}
|
|
169
|
+
if (param.length > 255) {
|
|
170
|
+
throw new MarvinValidationError(`Invalid ${paramName}: exceeds maximum length of 255 characters`);
|
|
171
|
+
}
|
|
172
|
+
return param;
|
|
94
173
|
}
|
|
95
174
|
/**
|
|
96
175
|
* Build full URL from endpoint
|
|
@@ -124,14 +203,78 @@ var HttpClient = class {
|
|
|
124
203
|
return filtered.length > 0 ? `?${filtered.join("&")}` : "";
|
|
125
204
|
}
|
|
126
205
|
/**
|
|
127
|
-
*
|
|
206
|
+
* Sanitize data for logging by redacting sensitive fields
|
|
207
|
+
*/
|
|
208
|
+
sanitizeForLogging(data) {
|
|
209
|
+
if (!data || typeof data !== "object") {
|
|
210
|
+
return data;
|
|
211
|
+
}
|
|
212
|
+
const sensitiveFields = [
|
|
213
|
+
"token",
|
|
214
|
+
"access_token",
|
|
215
|
+
"refresh_token",
|
|
216
|
+
"password",
|
|
217
|
+
"secret",
|
|
218
|
+
"apiKey",
|
|
219
|
+
"api_key",
|
|
220
|
+
"bearer",
|
|
221
|
+
"authorization",
|
|
222
|
+
"csrf_token",
|
|
223
|
+
"session",
|
|
224
|
+
"cookie"
|
|
225
|
+
];
|
|
226
|
+
const sanitize = (obj) => {
|
|
227
|
+
const sanitized = {};
|
|
228
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
229
|
+
const keyLower = key.toLowerCase();
|
|
230
|
+
const isSensitive = sensitiveFields.some((field) => keyLower.includes(field));
|
|
231
|
+
if (isSensitive) {
|
|
232
|
+
sanitized[key] = "[REDACTED]";
|
|
233
|
+
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
234
|
+
sanitized[key] = sanitize(value);
|
|
235
|
+
} else if (Array.isArray(value)) {
|
|
236
|
+
sanitized[key] = value.map(
|
|
237
|
+
(item) => item && typeof item === "object" ? sanitize(item) : item
|
|
238
|
+
);
|
|
239
|
+
} else {
|
|
240
|
+
sanitized[key] = value;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return sanitized;
|
|
244
|
+
};
|
|
245
|
+
if (Array.isArray(data)) {
|
|
246
|
+
return data.map(
|
|
247
|
+
(item) => item && typeof item === "object" ? sanitize(item) : item
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return sanitize(data);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Calculate delay for retry with exponential backoff
|
|
128
254
|
*/
|
|
129
|
-
|
|
255
|
+
calculateRetryDelay(attempt) {
|
|
256
|
+
const delay = this.retryConfig.initialDelay * Math.pow(2, attempt);
|
|
257
|
+
return Math.min(delay, this.retryConfig.maxDelay);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Sleep for specified milliseconds
|
|
261
|
+
*/
|
|
262
|
+
sleep(ms) {
|
|
263
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Perform HTTP request with automatic retry on transient failures
|
|
267
|
+
*/
|
|
268
|
+
async request(method, endpoint, options, retryAttempt = 0) {
|
|
130
269
|
const url = this.buildUrl(endpoint) + (options?.params ? this.buildQueryString(options.params) : "");
|
|
131
270
|
const isFormData = options?.body instanceof FormData;
|
|
132
271
|
const headers = this.buildHeaders(options?.headers, isFormData);
|
|
133
272
|
const controller = new AbortController();
|
|
134
273
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
274
|
+
const startTime = Date.now();
|
|
275
|
+
if (this.debug) {
|
|
276
|
+
this.logger.log(`[Marvin] ${method} ${url}`);
|
|
277
|
+
}
|
|
135
278
|
try {
|
|
136
279
|
const response = await fetch(url, {
|
|
137
280
|
method,
|
|
@@ -141,11 +284,54 @@ var HttpClient = class {
|
|
|
141
284
|
signal: controller.signal
|
|
142
285
|
});
|
|
143
286
|
clearTimeout(timeoutId);
|
|
287
|
+
const duration = Date.now() - startTime;
|
|
288
|
+
if (this.debug) {
|
|
289
|
+
this.logger.log(
|
|
290
|
+
`[Marvin] ${response.status} ${response.statusText} (${duration}ms)`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
144
293
|
if (response.status === 401 && this.auth.handleUnauthorized) {
|
|
145
294
|
await this.auth.handleUnauthorized();
|
|
146
295
|
}
|
|
147
296
|
if (!response.ok) {
|
|
148
297
|
const errorText = await response.text().catch(() => "");
|
|
298
|
+
if (this.debug) {
|
|
299
|
+
this.logger.error(
|
|
300
|
+
`[Marvin] Error response: ${response.status} ${response.statusText}`,
|
|
301
|
+
errorText ? `Body: ${errorText}` : ""
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const isRetryable = this.retryConfig.retryableStatuses.includes(response.status);
|
|
305
|
+
const canRetry = retryAttempt < this.retryConfig.maxRetries;
|
|
306
|
+
if (isRetryable && canRetry) {
|
|
307
|
+
const delay = this.calculateRetryDelay(retryAttempt);
|
|
308
|
+
if (this.debug) {
|
|
309
|
+
this.logger.warn(
|
|
310
|
+
`[Marvin] Retry ${retryAttempt + 1}/${this.retryConfig.maxRetries} after ${delay}ms (status: ${response.status})`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
await this.sleep(delay);
|
|
314
|
+
return this.request(method, endpoint, options, retryAttempt + 1);
|
|
315
|
+
}
|
|
316
|
+
if (response.status === 404) {
|
|
317
|
+
throw new MarvinNotFoundError(
|
|
318
|
+
`Resource not found: ${endpoint}`,
|
|
319
|
+
endpoint
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
if (response.status === 401 || response.status === 403) {
|
|
323
|
+
throw new MarvinAuthError(
|
|
324
|
+
`Authentication failed: ${response.statusText}`,
|
|
325
|
+
response.status
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
if (response.status >= 500) {
|
|
329
|
+
throw new MarvinServerError(
|
|
330
|
+
`Server error: ${response.status} ${response.statusText}`,
|
|
331
|
+
response.status,
|
|
332
|
+
endpoint
|
|
333
|
+
);
|
|
334
|
+
}
|
|
149
335
|
throw MarvinApiError.fromResponse(
|
|
150
336
|
response.status,
|
|
151
337
|
response.statusText,
|
|
@@ -157,21 +343,39 @@ var HttpClient = class {
|
|
|
157
343
|
if (!contentType || !contentType.includes("application/json")) {
|
|
158
344
|
return void 0;
|
|
159
345
|
}
|
|
160
|
-
|
|
346
|
+
const data = await response.json();
|
|
347
|
+
if (this.debug) {
|
|
348
|
+
const sanitized = this.sanitizeForLogging(data);
|
|
349
|
+
this.logger.log("[Marvin] Response:", JSON.stringify(sanitized, null, 2));
|
|
350
|
+
}
|
|
351
|
+
return data;
|
|
161
352
|
} catch (error) {
|
|
162
353
|
clearTimeout(timeoutId);
|
|
163
|
-
if (error instanceof MarvinApiError) {
|
|
354
|
+
if (error instanceof MarvinApiError || error instanceof MarvinNotFoundError || error instanceof MarvinAuthError || error instanceof MarvinServerError) {
|
|
164
355
|
throw error;
|
|
165
356
|
}
|
|
166
357
|
if (error instanceof Error) {
|
|
358
|
+
const canRetry = retryAttempt < this.retryConfig.maxRetries;
|
|
359
|
+
if (canRetry && error.name !== "AbortError") {
|
|
360
|
+
const delay = this.calculateRetryDelay(retryAttempt);
|
|
361
|
+
if (this.debug) {
|
|
362
|
+
this.logger.warn(
|
|
363
|
+
`[Marvin] Retry ${retryAttempt + 1}/${this.retryConfig.maxRetries} after ${delay}ms (network error)`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
await this.sleep(delay);
|
|
367
|
+
return this.request(method, endpoint, options, retryAttempt + 1);
|
|
368
|
+
}
|
|
167
369
|
if (error.name === "AbortError") {
|
|
168
|
-
throw new
|
|
370
|
+
throw new MarvinNetworkError(
|
|
169
371
|
`Request timeout after ${this.timeout}ms`,
|
|
170
|
-
|
|
171
|
-
endpoint
|
|
372
|
+
error
|
|
172
373
|
);
|
|
173
374
|
}
|
|
174
|
-
throw new
|
|
375
|
+
throw new MarvinNetworkError(
|
|
376
|
+
`Network error: ${error.message}`,
|
|
377
|
+
error
|
|
378
|
+
);
|
|
175
379
|
}
|
|
176
380
|
throw error;
|
|
177
381
|
}
|
|
@@ -216,10 +420,27 @@ var SessionAuth = class {
|
|
|
216
420
|
}
|
|
217
421
|
sessionToken;
|
|
218
422
|
onUnauthorized;
|
|
423
|
+
csrfToken;
|
|
424
|
+
/**
|
|
425
|
+
* Set CSRF token for cross-site request forgery protection
|
|
426
|
+
* The token should be obtained from the server after login
|
|
427
|
+
*/
|
|
428
|
+
setCsrfToken(token) {
|
|
429
|
+
this.csrfToken = token;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Get current CSRF token
|
|
433
|
+
*/
|
|
434
|
+
getCsrfToken() {
|
|
435
|
+
return this.csrfToken;
|
|
436
|
+
}
|
|
219
437
|
injectAuth(headers) {
|
|
220
438
|
if (this.sessionToken) {
|
|
221
439
|
headers.set("Authorization", `Bearer ${this.sessionToken}`);
|
|
222
440
|
}
|
|
441
|
+
if (this.csrfToken) {
|
|
442
|
+
headers.set("X-CSRF-Token", this.csrfToken);
|
|
443
|
+
}
|
|
223
444
|
}
|
|
224
445
|
async handleUnauthorized() {
|
|
225
446
|
if (this.onUnauthorized) {
|
|
@@ -227,7 +448,9 @@ var SessionAuth = class {
|
|
|
227
448
|
}
|
|
228
449
|
}
|
|
229
450
|
getDescription() {
|
|
230
|
-
|
|
451
|
+
const auth = this.sessionToken ? "Session authentication (explicit token)" : "Session authentication (cookies)";
|
|
452
|
+
const csrf = this.csrfToken ? " with CSRF protection" : "";
|
|
453
|
+
return auth + csrf;
|
|
231
454
|
}
|
|
232
455
|
};
|
|
233
456
|
|
|
@@ -247,7 +470,8 @@ var EntriesModule = class {
|
|
|
247
470
|
* Get a single entry by ID
|
|
248
471
|
*/
|
|
249
472
|
async get(id) {
|
|
250
|
-
|
|
473
|
+
const validId = this.http.validatePathParam(id, "entry ID");
|
|
474
|
+
return this.http.get(`/api/platform/entries/${validId}`);
|
|
251
475
|
}
|
|
252
476
|
/**
|
|
253
477
|
* Create a new entry
|
|
@@ -259,26 +483,31 @@ var EntriesModule = class {
|
|
|
259
483
|
* Update an entry
|
|
260
484
|
*/
|
|
261
485
|
async update(id, data) {
|
|
262
|
-
|
|
486
|
+
const validId = this.http.validatePathParam(id, "entry ID");
|
|
487
|
+
return this.http.patch(`/api/platform/entries/${validId}`, data);
|
|
263
488
|
}
|
|
264
489
|
/**
|
|
265
490
|
* Delete an entry
|
|
266
491
|
*/
|
|
267
492
|
async delete(id) {
|
|
268
|
-
|
|
493
|
+
const validId = this.http.validatePathParam(id, "entry ID");
|
|
494
|
+
return this.http.delete(`/api/platform/entries/${validId}`);
|
|
269
495
|
}
|
|
270
496
|
/**
|
|
271
497
|
* Get collections that this entry belongs to
|
|
272
498
|
*/
|
|
273
499
|
async listCollections(entryId) {
|
|
274
|
-
|
|
500
|
+
const validId = this.http.validatePathParam(entryId, "entry ID");
|
|
501
|
+
return this.http.get(`/api/platform/entries/${validId}/collections`);
|
|
275
502
|
}
|
|
276
503
|
/**
|
|
277
504
|
* Add entry to a collection
|
|
278
505
|
*/
|
|
279
506
|
async addToCollection(entryId, collectionId) {
|
|
507
|
+
const validEntryId = this.http.validatePathParam(entryId, "entry ID");
|
|
508
|
+
const validCollectionId = this.http.validatePathParam(collectionId, "collection ID");
|
|
280
509
|
return this.http.post(
|
|
281
|
-
`/api/platform/entries/${
|
|
510
|
+
`/api/platform/entries/${validEntryId}/collections/${validCollectionId}`,
|
|
282
511
|
{}
|
|
283
512
|
);
|
|
284
513
|
}
|
|
@@ -286,7 +515,9 @@ var EntriesModule = class {
|
|
|
286
515
|
* Remove entry from a collection
|
|
287
516
|
*/
|
|
288
517
|
async removeFromCollection(entryId, collectionId) {
|
|
289
|
-
|
|
518
|
+
const validEntryId = this.http.validatePathParam(entryId, "entry ID");
|
|
519
|
+
const validCollectionId = this.http.validatePathParam(collectionId, "collection ID");
|
|
520
|
+
return this.http.delete(`/api/platform/entries/${validEntryId}/collections/${validCollectionId}`);
|
|
290
521
|
}
|
|
291
522
|
};
|
|
292
523
|
|
|
@@ -306,7 +537,8 @@ var CollectionsModule = class {
|
|
|
306
537
|
* Get a single collection by ID
|
|
307
538
|
*/
|
|
308
539
|
async get(id) {
|
|
309
|
-
|
|
540
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
541
|
+
return this.http.get(`/api/platform/collections/${validId}`);
|
|
310
542
|
}
|
|
311
543
|
/**
|
|
312
544
|
* Create a new collection
|
|
@@ -318,25 +550,29 @@ var CollectionsModule = class {
|
|
|
318
550
|
* Update a collection
|
|
319
551
|
*/
|
|
320
552
|
async update(id, data) {
|
|
321
|
-
|
|
553
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
554
|
+
return this.http.patch(`/api/platform/collections/${validId}`, data);
|
|
322
555
|
}
|
|
323
556
|
/**
|
|
324
557
|
* Delete a collection
|
|
325
558
|
*/
|
|
326
559
|
async delete(id) {
|
|
327
|
-
|
|
560
|
+
const validId = this.http.validatePathParam(id, "collection ID");
|
|
561
|
+
return this.http.delete(`/api/platform/collections/${validId}`);
|
|
328
562
|
}
|
|
329
563
|
/**
|
|
330
564
|
* Get entries in a collection
|
|
331
565
|
*/
|
|
332
566
|
async getEntries(id) {
|
|
333
|
-
|
|
567
|
+
const validId = this.http.validatePathParam(id, "collection ID");
|
|
568
|
+
return this.http.get(`/api/platform/collections/${validId}/entries`);
|
|
334
569
|
}
|
|
335
570
|
/**
|
|
336
571
|
* Reorder entries in a collection
|
|
337
572
|
*/
|
|
338
573
|
async reorderEntries(id, entries) {
|
|
339
|
-
|
|
574
|
+
const validId = this.http.validatePathParam(id, "collection ID");
|
|
575
|
+
return this.http.patch(`/api/platform/collections/${validId}/entries/order`, { entries });
|
|
340
576
|
}
|
|
341
577
|
};
|
|
342
578
|
|
|
@@ -356,7 +592,8 @@ var ResourcesModule = class {
|
|
|
356
592
|
* Get a single resource by ID
|
|
357
593
|
*/
|
|
358
594
|
async get(id) {
|
|
359
|
-
|
|
595
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
596
|
+
return this.http.get(`/api/platform/resources/${validId}`);
|
|
360
597
|
}
|
|
361
598
|
/**
|
|
362
599
|
* Create a new resource
|
|
@@ -368,7 +605,8 @@ var ResourcesModule = class {
|
|
|
368
605
|
* Update a resource
|
|
369
606
|
*/
|
|
370
607
|
async update(id, data) {
|
|
371
|
-
|
|
608
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
609
|
+
return this.http.patch(`/api/platform/resources/${validId}`, data);
|
|
372
610
|
}
|
|
373
611
|
/**
|
|
374
612
|
* Delete a resource
|
|
@@ -390,6 +628,47 @@ var AssetsModule = class {
|
|
|
390
628
|
this.http = http;
|
|
391
629
|
}
|
|
392
630
|
http;
|
|
631
|
+
/**
|
|
632
|
+
* Validate file before upload
|
|
633
|
+
*/
|
|
634
|
+
validateFile(file, options) {
|
|
635
|
+
const maxSize = options?.maxFileSize || 100 * 1024 * 1024;
|
|
636
|
+
const allowedTypes = options?.allowedMimeTypes;
|
|
637
|
+
if (file.size === 0) {
|
|
638
|
+
throw new MarvinValidationError("File is empty");
|
|
639
|
+
}
|
|
640
|
+
if (file.size > maxSize) {
|
|
641
|
+
const maxMB = Math.round(maxSize / (1024 * 1024));
|
|
642
|
+
const fileMB = Math.round(file.size / (1024 * 1024));
|
|
643
|
+
throw new MarvinValidationError(
|
|
644
|
+
`File size (${fileMB}MB) exceeds maximum allowed size of ${maxMB}MB`
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
if (allowedTypes && allowedTypes.length > 0) {
|
|
648
|
+
if (file instanceof File && file.type) {
|
|
649
|
+
const isAllowed = allowedTypes.some((allowed) => {
|
|
650
|
+
if (allowed.endsWith("/*")) {
|
|
651
|
+
const prefix = allowed.slice(0, -2);
|
|
652
|
+
return file.type.startsWith(prefix);
|
|
653
|
+
}
|
|
654
|
+
return file.type === allowed;
|
|
655
|
+
});
|
|
656
|
+
if (!isAllowed) {
|
|
657
|
+
throw new MarvinValidationError(
|
|
658
|
+
`File type "${file.type}" is not allowed. Allowed types: ${allowedTypes.join(", ")}`
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (file instanceof File) {
|
|
664
|
+
if (!file.name || file.name.trim().length === 0) {
|
|
665
|
+
throw new MarvinValidationError("File must have a valid name");
|
|
666
|
+
}
|
|
667
|
+
if (file.name.includes("..") || file.name.includes("/") || file.name.includes("\\")) {
|
|
668
|
+
throw new MarvinValidationError("Invalid filename: cannot contain path separators");
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
393
672
|
/**
|
|
394
673
|
* List all assets
|
|
395
674
|
*/
|
|
@@ -400,16 +679,31 @@ var AssetsModule = class {
|
|
|
400
679
|
* Get a single asset by ID
|
|
401
680
|
*/
|
|
402
681
|
async get(id) {
|
|
403
|
-
|
|
682
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
683
|
+
return this.http.get(`/api/platform/assets/${validId}`);
|
|
404
684
|
}
|
|
405
685
|
/**
|
|
406
686
|
* Upload a new asset file
|
|
407
687
|
*
|
|
408
688
|
* @param file - The file to upload (File or Blob)
|
|
409
689
|
* @param metadata - Asset metadata (slug, name, altText, description, metadata)
|
|
690
|
+
* @param options - Upload options (file size limits, allowed types)
|
|
410
691
|
* @returns The created asset with extracted metadata
|
|
692
|
+
*
|
|
693
|
+
* @example
|
|
694
|
+
* ```typescript
|
|
695
|
+
* // Upload with default limits (100MB, all types)
|
|
696
|
+
* const asset = await assets.upload(file, { slug: 'my-image', name: 'My Image' });
|
|
697
|
+
*
|
|
698
|
+
* // Upload with restrictions
|
|
699
|
+
* const asset = await assets.upload(file,
|
|
700
|
+
* { slug: 'avatar', name: 'Avatar' },
|
|
701
|
+
* { maxFileSize: 5 * 1024 * 1024, allowedMimeTypes: ['image/*'] }
|
|
702
|
+
* );
|
|
703
|
+
* ```
|
|
411
704
|
*/
|
|
412
|
-
async upload(file, metadata) {
|
|
705
|
+
async upload(file, metadata, options) {
|
|
706
|
+
this.validateFile(file, options);
|
|
413
707
|
const formData = new FormData();
|
|
414
708
|
formData.append("file", file);
|
|
415
709
|
formData.append("slug", metadata.slug);
|
|
@@ -429,13 +723,24 @@ var AssetsModule = class {
|
|
|
429
723
|
* Update an asset (editable fields only: slug, name, altText, description, metadata)
|
|
430
724
|
*/
|
|
431
725
|
async update(id, data) {
|
|
432
|
-
|
|
726
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
727
|
+
return this.http.patch(`/api/platform/assets/${validId}`, data);
|
|
433
728
|
}
|
|
434
729
|
/**
|
|
435
730
|
* Delete an asset (removes file from storage and database)
|
|
436
731
|
*/
|
|
437
732
|
async delete(id) {
|
|
438
|
-
|
|
733
|
+
const validId = this.http.validatePathParam(id, "asset ID");
|
|
734
|
+
return this.http.delete(`/api/platform/assets/${validId}`);
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Get download URL for an asset file
|
|
738
|
+
* Use this to create download links or fetch the file directly
|
|
739
|
+
*/
|
|
740
|
+
getDownloadUrl(id, baseUrl) {
|
|
741
|
+
const validId = this.http.validatePathParam(id, "asset ID");
|
|
742
|
+
const base = baseUrl || (typeof window !== "undefined" && window.location ? window.location.origin : "");
|
|
743
|
+
return `${base}/api/platform/assets/${validId}/file`;
|
|
439
744
|
}
|
|
440
745
|
};
|
|
441
746
|
|
|
@@ -455,7 +760,8 @@ var APIClientsModule = class {
|
|
|
455
760
|
* Get a single API client by ID
|
|
456
761
|
*/
|
|
457
762
|
async get(id) {
|
|
458
|
-
|
|
763
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
764
|
+
return this.http.get(`/api/platform/api-clients/${validId}`);
|
|
459
765
|
}
|
|
460
766
|
/**
|
|
461
767
|
* Create a new API client (returns client with token)
|
|
@@ -467,7 +773,8 @@ var APIClientsModule = class {
|
|
|
467
773
|
* Update an API client
|
|
468
774
|
*/
|
|
469
775
|
async update(id, data) {
|
|
470
|
-
|
|
776
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
777
|
+
return this.http.patch(`/api/platform/api-clients/${validId}`, data);
|
|
471
778
|
}
|
|
472
779
|
/**
|
|
473
780
|
* Delete an API client
|
|
@@ -631,6 +938,15 @@ var WorkspacesModule = class {
|
|
|
631
938
|
const url = `/api/admin/groups/${id}${force ? "?force=true" : ""}`;
|
|
632
939
|
await this.http.delete(url);
|
|
633
940
|
}
|
|
941
|
+
/**
|
|
942
|
+
* Export workspace data as JSON (collections, entry types, entries, site config)
|
|
943
|
+
*/
|
|
944
|
+
async export(options) {
|
|
945
|
+
const pretty = options?.pretty ?? true;
|
|
946
|
+
const endpoint = pretty ? "/api/platform/workspace/export/pretty" : "/api/platform/workspace/export";
|
|
947
|
+
const params = options?.includeSystemTypes ? { include_system_types: true } : void 0;
|
|
948
|
+
return this.http.get(endpoint, params);
|
|
949
|
+
}
|
|
634
950
|
/**
|
|
635
951
|
* Get workspace preferences
|
|
636
952
|
*/
|
|
@@ -646,6 +962,12 @@ var WorkspacesModule = class {
|
|
|
646
962
|
};
|
|
647
963
|
|
|
648
964
|
// src/platform/invites.ts
|
|
965
|
+
function getEnv(key) {
|
|
966
|
+
if (typeof process !== "undefined" && process.env) {
|
|
967
|
+
return process.env[key];
|
|
968
|
+
}
|
|
969
|
+
return void 0;
|
|
970
|
+
}
|
|
649
971
|
var InvitesModule = class {
|
|
650
972
|
constructor(http) {
|
|
651
973
|
this.http = http;
|
|
@@ -678,27 +1000,47 @@ var InvitesModule = class {
|
|
|
678
1000
|
}
|
|
679
1001
|
/**
|
|
680
1002
|
* Generate invitation URL from a token
|
|
681
|
-
*
|
|
1003
|
+
*
|
|
1004
|
+
* @param token - Invitation token
|
|
1005
|
+
* @param baseUrl - Base URL for the frontend (required in browser environments)
|
|
1006
|
+
* @returns Full invitation URL
|
|
1007
|
+
*
|
|
1008
|
+
* @example
|
|
1009
|
+
* ```typescript
|
|
1010
|
+
* // Node.js (can use env vars)
|
|
1011
|
+
* const url = invites.getInvitationUrl(token);
|
|
1012
|
+
*
|
|
1013
|
+
* // Browser (must provide baseUrl)
|
|
1014
|
+
* const url = invites.getInvitationUrl(token, 'https://app.example.com');
|
|
1015
|
+
* ```
|
|
682
1016
|
*/
|
|
683
1017
|
getInvitationUrl(token, baseUrl) {
|
|
684
1018
|
let base = baseUrl;
|
|
685
1019
|
if (!base) {
|
|
686
|
-
base =
|
|
687
|
-
if (!base
|
|
688
|
-
const apiUrl =
|
|
689
|
-
if (apiUrl
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
1020
|
+
base = getEnv("MARVIN_FRONTEND_URL");
|
|
1021
|
+
if (!base) {
|
|
1022
|
+
const apiUrl = getEnv("MARVIN_API_URL");
|
|
1023
|
+
if (apiUrl) {
|
|
1024
|
+
if (apiUrl.includes(":8080")) {
|
|
1025
|
+
base = apiUrl.replace(":8080", ":4321");
|
|
1026
|
+
} else if (apiUrl.includes(":3000")) {
|
|
1027
|
+
base = apiUrl.replace(":3000", ":4321");
|
|
1028
|
+
} else {
|
|
1029
|
+
base = apiUrl;
|
|
1030
|
+
}
|
|
695
1031
|
}
|
|
696
1032
|
}
|
|
697
|
-
|
|
698
|
-
|
|
1033
|
+
}
|
|
1034
|
+
if (!base) {
|
|
1035
|
+
if (typeof window !== "undefined" && window.location) {
|
|
1036
|
+
base = window.location.origin;
|
|
1037
|
+
} else {
|
|
1038
|
+
throw new Error(
|
|
1039
|
+
'baseUrl is required when not in Node.js environment. Pass the frontend URL as second argument: getInvitationUrl(token, "https://app.example.com")'
|
|
1040
|
+
);
|
|
699
1041
|
}
|
|
700
1042
|
}
|
|
701
|
-
return `${base}/register?token=${token}`;
|
|
1043
|
+
return `${base}/register?token=${encodeURIComponent(token)}`;
|
|
702
1044
|
}
|
|
703
1045
|
};
|
|
704
1046
|
|
|
@@ -752,6 +1094,57 @@ var WebhooksModule = class {
|
|
|
752
1094
|
this.http = http;
|
|
753
1095
|
}
|
|
754
1096
|
http;
|
|
1097
|
+
/**
|
|
1098
|
+
* Validate webhook URL to prevent SSRF attacks
|
|
1099
|
+
*/
|
|
1100
|
+
validateWebhookUrl(url) {
|
|
1101
|
+
if (!url || typeof url !== "string") {
|
|
1102
|
+
throw new MarvinValidationError("Webhook URL is required and must be a string");
|
|
1103
|
+
}
|
|
1104
|
+
let parsed;
|
|
1105
|
+
try {
|
|
1106
|
+
parsed = new URL(url);
|
|
1107
|
+
} catch {
|
|
1108
|
+
throw new MarvinValidationError("Invalid webhook URL format. Expected: https://example.com/webhook");
|
|
1109
|
+
}
|
|
1110
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
1111
|
+
throw new MarvinValidationError(
|
|
1112
|
+
`Webhook URL must use HTTP or HTTPS protocol. Got: ${parsed.protocol}`
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
1116
|
+
const blockedHosts = [
|
|
1117
|
+
"localhost",
|
|
1118
|
+
"127.0.0.1",
|
|
1119
|
+
"0.0.0.0",
|
|
1120
|
+
"::1",
|
|
1121
|
+
"169.254.169.254",
|
|
1122
|
+
// AWS metadata service
|
|
1123
|
+
"[::]"
|
|
1124
|
+
];
|
|
1125
|
+
if (blockedHosts.some((blocked) => hostname === blocked || hostname.includes(blocked))) {
|
|
1126
|
+
throw new MarvinValidationError(
|
|
1127
|
+
"Webhook URL cannot target localhost or internal network addresses"
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
|
1131
|
+
const match = hostname.match(ipv4Regex);
|
|
1132
|
+
if (match) {
|
|
1133
|
+
const octets = match.slice(1).map(Number);
|
|
1134
|
+
if (octets[0] === 10) {
|
|
1135
|
+
throw new MarvinValidationError("Webhook URL cannot target private IP range (10.x.x.x)");
|
|
1136
|
+
}
|
|
1137
|
+
if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {
|
|
1138
|
+
throw new MarvinValidationError("Webhook URL cannot target private IP range (172.16-31.x.x)");
|
|
1139
|
+
}
|
|
1140
|
+
if (octets[0] === 192 && octets[1] === 168) {
|
|
1141
|
+
throw new MarvinValidationError("Webhook URL cannot target private IP range (192.168.x.x)");
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (url.length > 2048) {
|
|
1145
|
+
throw new MarvinValidationError("Webhook URL exceeds maximum length of 2048 characters");
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
755
1148
|
/**
|
|
756
1149
|
* List all webhooks for the current workspace
|
|
757
1150
|
*/
|
|
@@ -763,31 +1156,43 @@ var WebhooksModule = class {
|
|
|
763
1156
|
* Get a webhook by ID
|
|
764
1157
|
*/
|
|
765
1158
|
async get(id) {
|
|
766
|
-
|
|
1159
|
+
const validId = this.http.validatePathParam(id, "webhook ID");
|
|
1160
|
+
return this.http.get(`/api/groups/webhooks/${validId}`);
|
|
767
1161
|
}
|
|
768
1162
|
/**
|
|
769
1163
|
* Create a new webhook
|
|
1164
|
+
* Validates URL to prevent SSRF attacks
|
|
770
1165
|
*/
|
|
771
1166
|
async create(data) {
|
|
1167
|
+
if (data.url) {
|
|
1168
|
+
this.validateWebhookUrl(data.url);
|
|
1169
|
+
}
|
|
772
1170
|
return this.http.post("/api/groups/webhooks", data);
|
|
773
1171
|
}
|
|
774
1172
|
/**
|
|
775
1173
|
* Update a webhook
|
|
1174
|
+
* Validates URL if provided to prevent SSRF attacks
|
|
776
1175
|
*/
|
|
777
1176
|
async update(id, data) {
|
|
778
|
-
|
|
1177
|
+
const validId = this.http.validatePathParam(id, "webhook ID");
|
|
1178
|
+
if (data.url) {
|
|
1179
|
+
this.validateWebhookUrl(data.url);
|
|
1180
|
+
}
|
|
1181
|
+
return this.http.put(`/api/groups/webhooks/${validId}`, data);
|
|
779
1182
|
}
|
|
780
1183
|
/**
|
|
781
1184
|
* Delete a webhook
|
|
782
1185
|
*/
|
|
783
1186
|
async delete(id) {
|
|
784
|
-
|
|
1187
|
+
const validId = this.http.validatePathParam(id, "webhook ID");
|
|
1188
|
+
await this.http.delete(`/api/groups/webhooks/${validId}`);
|
|
785
1189
|
}
|
|
786
1190
|
/**
|
|
787
1191
|
* Test a webhook
|
|
788
1192
|
*/
|
|
789
1193
|
async test(id) {
|
|
790
|
-
|
|
1194
|
+
const validId = this.http.validatePathParam(id, "webhook ID");
|
|
1195
|
+
return this.http.post(`/api/groups/webhooks/${validId}/test`, {});
|
|
791
1196
|
}
|
|
792
1197
|
/**
|
|
793
1198
|
* Rerun failed webhooks
|
|
@@ -879,6 +1284,7 @@ var AdminSystemModule = class {
|
|
|
879
1284
|
}
|
|
880
1285
|
/**
|
|
881
1286
|
* Update email settings
|
|
1287
|
+
* Note: Password field is write-only and will not be returned in response
|
|
882
1288
|
*/
|
|
883
1289
|
async updateEmailSettings(data) {
|
|
884
1290
|
return this.http.put("/api/admin/email", data);
|
|
@@ -1162,53 +1568,40 @@ var EmailTemplatesClient = class {
|
|
|
1162
1568
|
http;
|
|
1163
1569
|
/**
|
|
1164
1570
|
* List all email templates for the workspace
|
|
1165
|
-
* @param groupId - Workspace ID
|
|
1166
1571
|
*/
|
|
1167
1572
|
async list(groupId) {
|
|
1168
1573
|
return this.http.get(`/api/platform/workspaces/${groupId}/email-templates`);
|
|
1169
1574
|
}
|
|
1170
1575
|
/**
|
|
1171
1576
|
* Get a specific email template
|
|
1172
|
-
* @param groupId - Workspace ID
|
|
1173
|
-
* @param templateId - Template ID
|
|
1174
1577
|
*/
|
|
1175
1578
|
async get(groupId, templateId) {
|
|
1176
1579
|
return this.http.get(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`);
|
|
1177
1580
|
}
|
|
1178
1581
|
/**
|
|
1179
1582
|
* Create a new email template
|
|
1180
|
-
* @param groupId - Workspace ID
|
|
1181
|
-
* @param data - Template data
|
|
1182
1583
|
*/
|
|
1183
1584
|
async create(groupId, data) {
|
|
1184
1585
|
return this.http.post(`/api/platform/workspaces/${groupId}/email-templates`, data);
|
|
1185
1586
|
}
|
|
1186
1587
|
/**
|
|
1187
1588
|
* Update an email template
|
|
1188
|
-
* @param groupId - Workspace ID
|
|
1189
|
-
* @param templateId - Template ID
|
|
1190
|
-
* @param data - Updated template data
|
|
1191
1589
|
*/
|
|
1192
1590
|
async update(groupId, templateId, data) {
|
|
1193
1591
|
return this.http.patch(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`, data);
|
|
1194
1592
|
}
|
|
1195
1593
|
/**
|
|
1196
1594
|
* Delete an email template
|
|
1197
|
-
* @param groupId - Workspace ID
|
|
1198
|
-
* @param templateId - Template ID
|
|
1199
1595
|
*/
|
|
1200
1596
|
async delete(groupId, templateId) {
|
|
1201
1597
|
await this.http.delete(`/api/platform/workspaces/${groupId}/email-templates/${templateId}`);
|
|
1202
1598
|
}
|
|
1203
1599
|
/**
|
|
1204
1600
|
* Send a test email using a template
|
|
1205
|
-
* @param groupId - Workspace ID
|
|
1206
|
-
* @param templateId - Template ID
|
|
1207
|
-
* @param recipientEmail - Test recipient email address
|
|
1208
1601
|
*/
|
|
1209
1602
|
async sendTest(groupId, templateId, recipientEmail) {
|
|
1210
1603
|
return this.http.post(`/api/platform/workspaces/${groupId}/email-templates/${templateId}/test`, {
|
|
1211
|
-
|
|
1604
|
+
email: recipientEmail
|
|
1212
1605
|
});
|
|
1213
1606
|
}
|
|
1214
1607
|
};
|
|
@@ -1229,7 +1622,8 @@ var ScheduledTasksModule = class {
|
|
|
1229
1622
|
* Get a scheduled task by ID or slug
|
|
1230
1623
|
*/
|
|
1231
1624
|
async get(idOrSlug) {
|
|
1232
|
-
|
|
1625
|
+
const validIdOrSlug = this.http.validatePathParam(idOrSlug, "idOrSlug");
|
|
1626
|
+
return this.http.get(`/api/platform/scheduled-tasks/${validIdOrSlug}`);
|
|
1233
1627
|
}
|
|
1234
1628
|
/**
|
|
1235
1629
|
* Create a new scheduled task
|
|
@@ -1241,7 +1635,8 @@ var ScheduledTasksModule = class {
|
|
|
1241
1635
|
* Update a scheduled task
|
|
1242
1636
|
*/
|
|
1243
1637
|
async update(idOrSlug, data) {
|
|
1244
|
-
|
|
1638
|
+
const validIdOrSlug = this.http.validatePathParam(idOrSlug, "idOrSlug");
|
|
1639
|
+
return this.http.patch(`/api/platform/scheduled-tasks/${validIdOrSlug}`, data);
|
|
1245
1640
|
}
|
|
1246
1641
|
/**
|
|
1247
1642
|
* Delete a scheduled task
|
|
@@ -1287,6 +1682,34 @@ var FormsModule = class {
|
|
|
1287
1682
|
this.http = http;
|
|
1288
1683
|
}
|
|
1289
1684
|
http;
|
|
1685
|
+
/**
|
|
1686
|
+
* Validate form submission data
|
|
1687
|
+
* - Prevents XSS by checking for script tags
|
|
1688
|
+
* - Validates data is a proper object
|
|
1689
|
+
* - Checks for reasonable size limits
|
|
1690
|
+
*/
|
|
1691
|
+
validateFormData(data) {
|
|
1692
|
+
if (!data || typeof data !== "object") {
|
|
1693
|
+
throw new MarvinValidationError("Form data must be a valid object");
|
|
1694
|
+
}
|
|
1695
|
+
const jsonString = JSON.stringify(data);
|
|
1696
|
+
const sizeInBytes = new Blob([jsonString]).size;
|
|
1697
|
+
if (sizeInBytes > 1024 * 1024) {
|
|
1698
|
+
throw new MarvinValidationError("Form data exceeds maximum size of 1MB");
|
|
1699
|
+
}
|
|
1700
|
+
const checkForScripts = (value) => {
|
|
1701
|
+
if (typeof value === "string") {
|
|
1702
|
+
if (/<script[\s\S]*?>[\s\S]*?<\/script>/gi.test(value)) {
|
|
1703
|
+
throw new MarvinValidationError("Form data contains potentially malicious content");
|
|
1704
|
+
}
|
|
1705
|
+
} else if (Array.isArray(value)) {
|
|
1706
|
+
value.forEach(checkForScripts);
|
|
1707
|
+
} else if (value && typeof value === "object") {
|
|
1708
|
+
Object.values(value).forEach(checkForScripts);
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
Object.values(data).forEach(checkForScripts);
|
|
1712
|
+
}
|
|
1290
1713
|
/**
|
|
1291
1714
|
* List all forms
|
|
1292
1715
|
*/
|
|
@@ -1297,7 +1720,8 @@ var FormsModule = class {
|
|
|
1297
1720
|
* Get a single form by ID
|
|
1298
1721
|
*/
|
|
1299
1722
|
async get(id) {
|
|
1300
|
-
|
|
1723
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
1724
|
+
return this.http.get(`/api/platform/forms/${validId}`);
|
|
1301
1725
|
}
|
|
1302
1726
|
/**
|
|
1303
1727
|
* Create a new form
|
|
@@ -1309,7 +1733,8 @@ var FormsModule = class {
|
|
|
1309
1733
|
* Update a form
|
|
1310
1734
|
*/
|
|
1311
1735
|
async update(id, data) {
|
|
1312
|
-
|
|
1736
|
+
const validId = this.http.validatePathParam(id, "id");
|
|
1737
|
+
return this.http.patch(`/api/platform/forms/${validId}`, data);
|
|
1313
1738
|
}
|
|
1314
1739
|
/**
|
|
1315
1740
|
* Delete a form
|
|
@@ -1331,15 +1756,31 @@ var FormsModule = class {
|
|
|
1331
1756
|
}
|
|
1332
1757
|
/**
|
|
1333
1758
|
* Submit data to a published form (Publishing API - public access)
|
|
1759
|
+
* Validates data before submission to prevent XSS and malicious content
|
|
1334
1760
|
*/
|
|
1335
1761
|
async submitForm(slug, data) {
|
|
1336
|
-
|
|
1762
|
+
const validSlug = this.http.validatePathParam(slug, "form slug");
|
|
1763
|
+
this.validateFormData(data);
|
|
1764
|
+
return this.http.post(`/api/publish/forms/${validSlug}/submit`, {
|
|
1337
1765
|
dataJson: data
|
|
1338
1766
|
});
|
|
1339
1767
|
}
|
|
1340
1768
|
};
|
|
1341
1769
|
|
|
1342
1770
|
// src/platform/client.ts
|
|
1771
|
+
function getEnv2(key) {
|
|
1772
|
+
if (typeof process !== "undefined" && process.env) {
|
|
1773
|
+
return process.env[key];
|
|
1774
|
+
}
|
|
1775
|
+
return void 0;
|
|
1776
|
+
}
|
|
1777
|
+
function createPlatformConfigFromEnv(overrides) {
|
|
1778
|
+
return {
|
|
1779
|
+
apiUrl: overrides?.apiUrl || getEnv2("MARVIN_API_URL") || "http://localhost:8080",
|
|
1780
|
+
userToken: overrides?.userToken || getEnv2("MARVIN_USER_TOKEN"),
|
|
1781
|
+
credentials: overrides?.credentials || "include"
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1343
1784
|
var PlatformClient = class extends HttpClient {
|
|
1344
1785
|
entries;
|
|
1345
1786
|
collections;
|
|
@@ -1366,7 +1807,7 @@ var PlatformClient = class extends HttpClient {
|
|
|
1366
1807
|
adminMaintenance;
|
|
1367
1808
|
constructor(config) {
|
|
1368
1809
|
const httpConfig = {
|
|
1369
|
-
baseUrl: config.apiUrl,
|
|
1810
|
+
baseUrl: config.apiUrl || "http://localhost:8080",
|
|
1370
1811
|
auth: new SessionAuth(config.userToken),
|
|
1371
1812
|
credentials: config.credentials || "include"
|
|
1372
1813
|
};
|
|
@@ -1396,7 +1837,8 @@ var PlatformClient = class extends HttpClient {
|
|
|
1396
1837
|
}
|
|
1397
1838
|
};
|
|
1398
1839
|
function createPlatformClient(config) {
|
|
1399
|
-
|
|
1840
|
+
const finalConfig = createPlatformConfigFromEnv(config);
|
|
1841
|
+
return new PlatformClient(finalConfig);
|
|
1400
1842
|
}
|
|
1401
1843
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1402
1844
|
0 && (module.exports = {
|
|
@@ -1423,5 +1865,6 @@ function createPlatformClient(config) {
|
|
|
1423
1865
|
WebhooksModule,
|
|
1424
1866
|
WorkspaceMembersModule,
|
|
1425
1867
|
WorkspacesModule,
|
|
1426
|
-
createPlatformClient
|
|
1868
|
+
createPlatformClient,
|
|
1869
|
+
createPlatformConfigFromEnv
|
|
1427
1870
|
});
|