Spaces:
Sleeping
Sleeping
| ; | |
| const API_PREFIX = '/api'; | |
| let __boxStatusCache = null; | |
| let __boxStatusFetchedAt = 0; | |
| function stripPhotoDataUrl(value) { | |
| if (Array.isArray(value)) { | |
| return value.map((item) => stripPhotoDataUrl(item)); | |
| } | |
| if (!value || typeof value !== 'object') { | |
| return value; | |
| } | |
| const clone = {}; | |
| for (const [key, child] of Object.entries(value)) { | |
| if (key === 'photoDataUrl') continue; | |
| clone[key] = stripPhotoDataUrl(child); | |
| } | |
| return clone; | |
| } | |
| async function parseApiResponse(response) { | |
| let payload = null; | |
| try { | |
| payload = await response.json(); | |
| } catch { | |
| payload = null; | |
| } | |
| if (!response.ok) { | |
| throw new Error(payload?.error || payload?.message || `Request failed with HTTP ${response.status}`); | |
| } | |
| return payload; | |
| } | |
| async function apiRequest(path, options = {}) { | |
| const response = await fetch(`${API_PREFIX}${path}`, { | |
| method: options.method || 'GET', | |
| headers: { | |
| ...(options.rawBody ? {} : { 'Content-Type': 'application/json' }), | |
| ...(options.headers || {}), | |
| }, | |
| ...(options.body !== undefined ? { body: options.body } : {}), | |
| }); | |
| return parseApiResponse(response); | |
| } | |
| async function getBoxStatus(options = {}) { | |
| const { force = false } = options; | |
| if (__boxStatusCache && !force && (Date.now() - __boxStatusFetchedAt) < 10000) { | |
| return __boxStatusCache; | |
| } | |
| const payload = await apiRequest('/box/status', { method: 'GET' }); | |
| __boxStatusCache = payload?.status || {}; | |
| __boxStatusFetchedAt = Date.now(); | |
| return __boxStatusCache; | |
| } | |
| function buildMissingConfigMessage(status = {}) { | |
| if (!status.hasAuth) { | |
| return 'Box authentication is not configured on the backend. Open settings.html and save the Box integration first.'; | |
| } | |
| if (!status.hasIntakeFolderId) { | |
| return 'Incoming intake folder ID is not configured. Open settings.html and save the Box integration first.'; | |
| } | |
| return 'Box is not configured on the backend. Open settings.html and save the Box integration first.'; | |
| } | |
| async function testBoxConnection() { | |
| try { | |
| const payload = await apiRequest('/box/test-connection', { | |
| method: 'POST', | |
| body: JSON.stringify({}), | |
| }); | |
| __boxStatusCache = null; | |
| __boxStatusFetchedAt = 0; | |
| return payload; | |
| } catch (error) { | |
| return { ok: false, error: error.message }; | |
| } | |
| } | |
| async function uploadPhoto(file, caseId) { | |
| if (!file) { | |
| throw new Error('Photo file is required.'); | |
| } | |
| const status = await getBoxStatus(); | |
| if (!status.configured) { | |
| throw new Error(buildMissingConfigMessage(status)); | |
| } | |
| const payload = await apiRequest(`/box/upload-photo?caseId=${encodeURIComponent(caseId)}`, { | |
| method: 'POST', | |
| rawBody: true, | |
| headers: { | |
| 'Content-Type': file.type || 'application/octet-stream', | |
| }, | |
| body: file, | |
| }); | |
| return { | |
| fileId: payload.fileId || '', | |
| sharedLink: payload.sharedLink || null, | |
| photoUrl: payload.photoUrl || '', | |
| }; | |
| } | |
| async function uploadSidecar(metadata, caseId) { | |
| const status = await getBoxStatus(); | |
| if (!status.configured) { | |
| throw new Error(buildMissingConfigMessage(status)); | |
| } | |
| const payload = await apiRequest('/box/upload-sidecar', { | |
| method: 'POST', | |
| body: JSON.stringify({ | |
| caseId, | |
| metadata: stripPhotoDataUrl(metadata || {}), | |
| }), | |
| }); | |
| return { | |
| fileId: payload.fileId || '', | |
| sidecarPayload: payload.sidecarPayload || null, | |
| }; | |
| } | |
| async function submitToBox(photoFile, metadata) { | |
| const providedCaseId = metadata && typeof metadata.caseId === 'string' | |
| ? metadata.caseId.trim() | |
| : ''; | |
| const randomSuffix = Math.random() | |
| .toString(36) | |
| .toUpperCase() | |
| .replace(/[^A-Z]/g, '') | |
| .slice(0, 4) | |
| .padEnd(4, 'X'); | |
| const caseId = providedCaseId || `PHX-${Date.now()}-${randomSuffix}`; | |
| const submittedAt = metadata?.submittedAt || new Date().toISOString(); | |
| const sanitizedMetadata = stripPhotoDataUrl({ | |
| ...metadata, | |
| caseId, | |
| submittedAt, | |
| }); | |
| try { | |
| const photoResult = await uploadPhoto(photoFile, caseId); | |
| const sidecarResult = await uploadSidecar({ | |
| ...sanitizedMetadata, | |
| photoFileId: photoResult.fileId || '', | |
| photoUrl: photoResult.photoUrl || '', | |
| }, caseId); | |
| return { | |
| success: true, | |
| caseId, | |
| photoFileId: photoResult.fileId || '', | |
| sidecarFileId: sidecarResult.fileId || '', | |
| sidecarPayload: sidecarResult.sidecarPayload || sanitizedMetadata, | |
| }; | |
| } catch (error) { | |
| return { | |
| success: false, | |
| error: error instanceof Error ? error.message : String(error), | |
| }; | |
| } | |
| } | |
| if (typeof module !== 'undefined' && module.exports) { | |
| module.exports = { | |
| getBoxStatus, | |
| testBoxConnection, | |
| uploadPhoto, | |
| uploadSidecar, | |
| submitToBox, | |
| stripPhotoDataUrl, | |
| }; | |
| } | |
| if (typeof window !== 'undefined') { | |
| window.getBoxStatus = getBoxStatus; | |
| window.testBoxConnection = testBoxConnection; | |
| window.submitToBox = submitToBox; | |
| window.BoxUpload = { | |
| getBoxStatus, | |
| testBoxConnection, | |
| uploadPhoto, | |
| uploadSidecar, | |
| submitToBox, | |
| stripPhotoDataUrl, | |
| }; | |
| } | |