UNPKG

@mikezimm/fps-core-v7

Version:

Library of reusable core interfaces, types and constants migrated from fps-library-v2

195 lines (178 loc) 9.43 kB
/** * 2024-09-15: Migrated to SAME FOLDER in fps-library-v2\src\components\molecules\SpHttp... * export { IJSFetchReturn, createEmptyFetchReturn, doSpJsFetch } */ // import { check4This } from "@mikezimm/fps-core-v7/lib/logic/Links/CheckSearch"; // import { IFPSResultStatus } from "@mikezimm/fps-pnp2/lib/services/sp/IFPSResultStatus"; // import { IFpsErrorObject } from "@mikezimm/fps-core-v7/lib/types/fps-returns/common/IFpsErrorObject"; //./../../pnpjs/Common/IFpsErrorObject"; import { check4This, Check4 } from "../../../../logic/Links/CheckSearch"; import { makeAbsoluteUrl } from "../../../../logic/Strings/getSiteCollectionUrlFromLink"; import { getSiteCollectionUrlFromLink } from "../../../../logic/Strings/getSiteCollectionUrlFromLink"; import { CurrentHostName } from "../../source-props/WindowLocationConstants"; // import { CurrentHostName } from "../../../logic/Strings/getSiteCollectionUrlFromLink"; import { check4ThisFPSDigestValue } from "../helpers/check4ThisFPSDigestValue"; import { createEmptyFetchReturn } from "../interfaces/IJSFetchReturn"; const acceptVerbose = 'application/json;odata=verbose'; const acceptNoMeta = 'application/json;odata=nometadata'; const EmptySpJsFetchObject = { api: '', method: 'GET', version: 'HTTP/1.1', accept: 'application/json;odata=verbose', ctype: 'application/json;odata=verbose', }; export async function doSpJsBatchFetchOrPost(batchFetchAPIs, digestValue) { // Automatically added this because API's usually need full url. const validFetchAPIs = batchFetchAPIs.map((batchItem) => { // Always make sure this is an absolute url by running through this first. Note, if the previous line executes, it should be good then. let thisAPI = makeAbsoluteUrl(batchItem.api); if (thisAPI.indexOf(CurrentHostName) === 0) thisAPI = `https://${thisAPI}`; // Merge all defaults into the api object return { ...EmptySpJsFetchObject, ...{ api: thisAPI } }; }); // Added to just check if digestValue exists and if so, use it here if (!digestValue) { digestValue = check4ThisFPSDigestValue(getSiteCollectionUrlFromLink(validFetchAPIs[0].api)); } const headers = { Accept: acceptVerbose, "Content-Type": "multipart/mixed; boundary=batch_boundary", // "Authorization": "Bearer YOUR_ACCESS_TOKEN" // Use an access token if required }; const batchStrings = validFetchAPIs.map(batchItem => { return ` --batch_boundary Content-Type: application/http Content-Transfer-Encoding: binary ${batchItem.method} ${batchItem.api} ${batchItem.version} ${batchItem.ctype ? `Content-Type: ${batchItem.ctype}` : ''} ${digestValue ? `X-RequestDigest: ${digestValue}` : ''} ${batchItem.body ? `body: ${JSON.stringify(batchItem.body)}` : ''} Accept: ${batchItem.accept}`; /** * Minimal Example for Clarity If you only include one GET and one POST, your batch body might look like this: plaintext Copy code --batch_boundary Content-Type: application/http Content-Transfer-Encoding: binary GET /sites/WorldMaps/_api/web/lists/getbytitle('WorldCoords888')/items?$top=5 HTTP/1.1 Accept: application/json;odata=verbose --batch_boundary Content-Type: application/http Content-Transfer-Encoding: binary POST /sites/WorldMaps/_api/web/lists/getbytitle('WorldCoords888')/items HTTP/1.1 Accept: application/json;odata=verbose Content-Type: application/json;odata=verbose X-RequestDigest: <your_digest_value> { "Title": "New Item Title" } --batch_boundary-- Why Each Part Requires Headers Each batch part is treated as a complete, independent HTTP request: The server doesn't "know" what each part is without being told explicitly. The Content-Type and Content-Transfer-Encoding ensure the server processes each section appropriately. Can It Be Omitted? No, these headers are mandatory for SharePoint's REST Batch API to correctly interpret the request structure. If omitted, you will receive errors indicating an improperly formatted batch request. While it might feel redundant, these headers are part of the multipart/mixed specification, which SharePoint adheres to strictly. * * Why Are They Always the Same? Content-Type: application/http: This header indicates that the content of each part is a raw HTTP request, regardless of whether it's a GET, POST, PATCH, or DELETE. SharePoint expects this format for every batch request. Content-Transfer-Encoding: binary: This header signals that the body of each request is in a raw binary form (not encoded, such as Base64). Since all batch parts are treated as raw HTTP requests, this encoding is fixed and does not depend on the specific API operation. */ }); const batchBody = `${batchStrings.join('--batch_boundary')}-batch_boundary`; const batchEndpoint = validFetchAPIs[0].api.split('_')[0]; try { const response = await fetch(`${batchEndpoint}/_api/$batch`, { method: 'POST', headers: headers, body: batchBody, }); let results = []; // check if the response is OK if (response.ok) { const data = await parseBatchResponse(response); console.log(data); validFetchAPIs.map(batchItem => { return createEmptyFetchReturn(batchItem.api, batchItem.method); }); // DO Additional logic on each item in data here: // const usesSearch: boolean = fetchAPI.indexOf(`_api/search`) > -1 ? true : false; // if ( usesSearch ) results.rawSearchResults = data; // const deepPropValue = usesSearch === true ? checkDeepProperty( data, [ 'PrimaryQueryResult', 'RelevantResults', 'Table', 'Rows'], 'Actual' ) : undefined; // // added logic to solve this: https://github.com/mikezimm/pivottiles7/issues/292 // if ( deepPropValue !== undefined && deepPropValue !== null ) { // results.items = data.PrimaryQueryResult.RelevantResults.Table.Rows; // } else if ( fetchAPI.indexOf('GetFolderByServerRelativeUrl') > 0 ) { // } else { // if ( usesSearch === true && data.ElapsedTime && !data.PrimaryQueryResult ) { // // Seems like query did not fail, so do nothing because array of items is already []. // // This is what happened when using fetchMySubsites and not having any subsites to return. // } else { // results.items = data.value ? data.value : data; // } // } // results.ok = true; // results.statusText = response.statusText; // results.statusNo = response.status; // results.status = 'Success'; if (check4This(Check4.fpsShowFetchResults_Eq_true) === true) console.log(`fps-core-v7 Success: doSpJsFetchOrPost ~ 131 results`, results); } else { console.error(`Batch request failed: ~ 172 ${response.status}`); // results = await addCatchResponseError( results, response, digestValue ); } return results; } catch (e) { console.error(`Batch request failed: ~ 181 ${e}`); return []; // return addUnknownFetchError( results, e ); } } async function parseBatchResponse(response) { var _a; const contentType = response.headers.get("Content-Type"); const boundary = (_a = contentType.match(/boundary=(.+)$/)) === null || _a === void 0 ? void 0 : _a[1]; if (!boundary) { throw new Error("Unable to find boundary in response Content-Type header."); } const rawText = await response.text(); // Split the response by the boundary const parts = rawText.split(`--${boundary}`).filter((part) => part.trim() && part.trim() !== '--'); const parsedResults = parts.map((part) => { const [rawHeaders, rawBody] = part.split("\r\n\r\n"); // Parse headers const headers = {}; rawHeaders.split("\r\n").forEach((header) => { const [key, value] = header.split(": "); if (key && value) { headers[`${key.trim()}`] = value.trim(); } }); // Parse JSON body if present let body; if (headers["Content-Type"] && headers["Content-Type"].includes("application/json")) { try { body = JSON.parse(rawBody.trim()); } catch (e) { body = rawBody.trim(); } } else { body = rawBody.trim(); } return { headers, body }; }); return parsedResults; } //# sourceMappingURL=doSpJsBatchFetch.js.map