@proofkit/fmodata
Version:
FileMaker OData API client
253 lines (252 loc) • 7.39 kB
JavaScript
function generateBoundary(prefix = "batch_") {
const randomHex = Array.from(
{ length: 32 },
() => Math.floor(Math.random() * 16).toString(16)
).join("");
return `${prefix}${randomHex}`;
}
async function requestToConfig(request) {
const headers = {};
request.headers.forEach((value, key) => {
headers[key] = value;
});
let body;
if (request.body) {
const clonedRequest = request.clone();
body = await clonedRequest.text();
}
return {
method: request.method,
url: request.url,
body,
headers
};
}
function formatSubRequest(request, baseUrl) {
const lines = [];
lines.push("Content-Type: application/http");
lines.push("Content-Transfer-Encoding: binary");
lines.push("");
const fullUrl = request.url.startsWith("http") ? request.url : `${baseUrl}${request.url}`;
lines.push(`${request.method} ${fullUrl} HTTP/1.1`);
if (request.body) {
if (request.headers) {
for (const [key, value] of Object.entries(request.headers)) {
if (key.toLowerCase() !== "authorization") {
lines.push(`${key}: ${value}`);
}
}
}
const hasContentType = request.headers && Object.keys(request.headers).some(
(k) => k.toLowerCase() === "content-type"
);
if (!hasContentType) {
lines.push("Content-Type: application/json");
}
const hasContentLength = request.headers && Object.keys(request.headers).some(
(k) => k.toLowerCase() === "content-length"
);
if (!hasContentLength) {
lines.push(`Content-Length: ${request.body.length}`);
}
lines.push("");
lines.push(request.body);
} else {
lines.push("");
lines.push("");
}
return lines.join("\r\n");
}
function formatChangeset(requests, baseUrl, changesetBoundary) {
const lines = [];
lines.push(`Content-Type: multipart/mixed; boundary=${changesetBoundary}`);
lines.push("");
for (const request of requests) {
lines.push(`--${changesetBoundary}`);
lines.push(formatSubRequest(request, baseUrl));
}
lines.push(`--${changesetBoundary}--`);
return lines.join("\r\n");
}
async function formatBatchRequestFromNative(requests, baseUrl, batchBoundary) {
const boundary = batchBoundary || generateBoundary("batch_");
const lines = [];
for (const item of requests) {
if (Array.isArray(item)) {
const changesetBoundary = generateBoundary("changeset_");
const changesetConfigs = [];
for (const request of item) {
changesetConfigs.push(await requestToConfig(request));
}
lines.push(`--${boundary}`);
lines.push(formatChangeset(changesetConfigs, baseUrl, changesetBoundary));
} else {
const config = await requestToConfig(item);
if (config.method === "GET") {
lines.push(`--${boundary}`);
lines.push(formatSubRequest(config, baseUrl));
} else {
const changesetBoundary = generateBoundary("changeset_");
lines.push(`--${boundary}`);
lines.push(formatChangeset([config], baseUrl, changesetBoundary));
}
}
}
lines.push(`--${boundary}--`);
return {
body: lines.join("\r\n"),
boundary
};
}
function extractBoundary(contentType) {
const match = contentType.match(/boundary=([^;]+)/);
return match && match[1] ? match[1].trim() : null;
}
function parseStatusLine(line) {
var _a;
const match = line.match(/HTTP\/\d\.\d\s+(\d+)\s*(.*)/);
if (!match || !match[1]) {
return { status: 0, statusText: "" };
}
return {
status: parseInt(match[1], 10),
statusText: ((_a = match[2]) == null ? void 0 : _a.trim()) || ""
};
}
function parseHeaders(lines) {
const headers = {};
for (const line of lines) {
const colonIndex = line.indexOf(":");
if (colonIndex > 0) {
const key = line.substring(0, colonIndex).trim();
const value = line.substring(colonIndex + 1).trim();
headers[key.toLowerCase()] = value;
}
}
return headers;
}
function parseHttpResponse(part) {
const lines = part.split(/\r\n/);
let statusLineIndex = -1;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line && line.startsWith("HTTP/")) {
statusLineIndex = i;
break;
}
}
if (statusLineIndex === -1) {
return {
status: 0,
statusText: "Invalid response",
headers: {},
body: null
};
}
const statusLine = lines[statusLineIndex];
if (!statusLine) {
return {
status: 0,
statusText: "Invalid response",
headers: {},
body: null
};
}
const { status, statusText } = parseStatusLine(statusLine);
const headerLines = [];
let bodyStartIndex = lines.length;
let foundEmptyLine = false;
for (let i = statusLineIndex + 1; i < lines.length; i++) {
const line = lines[i];
if (line === "") {
bodyStartIndex = i + 1;
foundEmptyLine = true;
break;
}
if (line && line.startsWith("--")) {
break;
}
if (line) {
headerLines.push(line);
}
}
const headers = parseHeaders(headerLines);
let bodyText = "";
if (foundEmptyLine && bodyStartIndex < lines.length) {
const bodyLines = lines.slice(bodyStartIndex);
const bodyLinesFiltered = [];
for (const line of bodyLines) {
if (line.startsWith("--")) {
break;
}
bodyLinesFiltered.push(line);
}
bodyText = bodyLinesFiltered.join("\r\n").trim();
}
let body = null;
if (bodyText) {
try {
body = JSON.parse(bodyText);
} catch {
body = bodyText;
}
}
return {
status,
statusText,
headers,
body
};
}
function parseBatchResponse(responseText, contentType) {
var _a;
const boundary = extractBoundary(contentType);
if (!boundary) {
throw new Error("Could not extract boundary from Content-Type header");
}
const results = [];
const boundaryPattern = `--${boundary}`;
const parts = responseText.split(boundaryPattern);
for (const part of parts) {
const trimmedPart = part.trim();
if (!trimmedPart || trimmedPart === "--") {
continue;
}
if (trimmedPart.includes("Content-Type: multipart/mixed")) {
const changesetContentTypeMatch = trimmedPart.match(
/Content-Type: multipart\/mixed;\s*boundary=([^\r\n]+)/
);
if (changesetContentTypeMatch) {
const changesetBoundary = (_a = changesetContentTypeMatch == null ? void 0 : changesetContentTypeMatch[1]) == null ? void 0 : _a.trim();
const changesetPattern = `--${changesetBoundary}`;
const changesetParts = trimmedPart.split(changesetPattern);
for (const changesetPart of changesetParts) {
const trimmedChangesetPart = changesetPart.trim();
if (!trimmedChangesetPart || trimmedChangesetPart === "--") {
continue;
}
if (trimmedChangesetPart.startsWith("Content-Type: multipart/mixed")) {
continue;
}
const response = parseHttpResponse(trimmedChangesetPart);
if (response.status > 0) {
results.push(response);
}
}
}
} else {
const response = parseHttpResponse(trimmedPart);
if (response.status > 0) {
results.push(response);
}
}
}
return results;
}
export {
extractBoundary,
formatBatchRequestFromNative,
generateBoundary,
parseBatchResponse
};
//# sourceMappingURL=batch-request.js.map