UNPKG

@kwiz/common

Version:

KWIZ common utilities and helpers for M365 platform

503 lines 23.5 kB
import { GetError } from "../../exports-index"; import { jsonStringify } from "../../helpers/json"; import { isNotEmptyArray, isNotEmptyString, isNullOrEmptyString, isNullOrUndefined, isNumber, isNumeric, newGuid } from "../../helpers/typecheckers"; import { encodeURIComponentEX, makeServerRelativeUrl, normalizeUrl } from "../../helpers/url"; import { jsonTypes } from "../../types/rest.types"; import { ConsoleLogger } from "../consolelogger"; import { GetJson, GetJsonSync, longLocalCache, mediumLocalCache, noLocalCache, shortLocalCache } from "../rest"; import { GetRestBaseUrl, GetSiteUrl } from "./common"; import { GetListRestUrl } from "./list"; const logger = ConsoleLogger.get("SharePoint.Rest.FileNFolder"); let existingFolders = []; export async function EnsureFolderPath(siteUrl, folderServerRelativeUrl) { siteUrl = GetSiteUrl(siteUrl); //issue 7176 folderServerRelativeUrl = makeServerRelativeUrl(folderServerRelativeUrl, siteUrl); if (existingFolders.indexOf(folderServerRelativeUrl) >= 0) { return true; } let url = `${GetRestBaseUrl(siteUrl)}/Web/getFolderByServerRelativeUrl(serverRelativeUrl='${folderServerRelativeUrl}')?$select=exists`; let folder = await GetJson(url); if (folder && folder.d.Exists) { existingFolders.push(folderServerRelativeUrl); return true; } else { let parts = folderServerRelativeUrl.split('/'); if (parts.length > 1) { let parentFolder = parts.slice(0, parts.length - 1).join('/'); //ensure parent let parent = await EnsureFolderPath(siteUrl, parentFolder); if (parent) { //create it let ensure = await EnsureFolder(siteUrl, parentFolder, parts[parts.length - 1]); if (ensure.Exists) { existingFolders.push(folderServerRelativeUrl); return true; } } } } return false; } export function EnsureFolder(siteUrl, parentFolderServerRelativeUrl, folderName) { siteUrl = GetSiteUrl(siteUrl); parentFolderServerRelativeUrl = makeServerRelativeUrl(parentFolderServerRelativeUrl, siteUrl); return GetJson(`${GetRestBaseUrl(siteUrl)}/Web/getFolderByServerRelativeUrl(serverRelativeUrl='${parentFolderServerRelativeUrl}')/folders/add(url='${folderName}')`, null, { method: "POST", spWebUrl: siteUrl }) .then(r => { return r.d; }) .catch(() => { return { Exists: false }; }); } export function DeleteFolder(siteUrl, folderUrl) { siteUrl = GetSiteUrl(siteUrl); folderUrl = makeServerRelativeUrl(folderUrl, siteUrl); var requestUrl = `${GetRestBaseUrl(siteUrl)}/Web/getFolderByServerRelativeUrl(serverRelativeUrl='${folderUrl}')`; return GetJson(requestUrl, null, { method: "POST", xHttpMethod: "DELETE" }) .then(r => true) .catch((e) => false); } export function GetFolderFiles(siteUrl, folderUrl) { siteUrl = GetSiteUrl(siteUrl); folderUrl = makeServerRelativeUrl(folderUrl, siteUrl); var requestUrl = `${GetRestBaseUrl(siteUrl)}/Web/getFolderByServerRelativeUrl(serverRelativeUrl='${folderUrl}')` + `/files?$select=Level,Exists,Name,ServerRelativeUrl,Title,TimeCreated,TimeLastModified,ListItemAllFields/OData__ModerationStatus&$expand=ListItemAllFields`; return GetJson(requestUrl).then(r => { return r.d && r.d.results || []; }).catch(() => { return []; }); } export function UploadFileSync(siteUrl, folderServerRelativeUrl, fileName, fileContent) { siteUrl = GetSiteUrl(siteUrl); folderServerRelativeUrl = makeServerRelativeUrl(folderServerRelativeUrl, siteUrl); let res = GetJsonSync(`${GetRestBaseUrl(siteUrl)}/Web/getFolderByServerRelativeUrl(serverRelativeUrl='${folderServerRelativeUrl}')/files/add(url='${fileName}',overwrite=true)?$expand=ListItemAllFields`, fileContent, { method: 'POST', spWebUrl: siteUrl }); return res.success && res.result && res.result.d ? res.result.d : { Exists: false }; } export async function UploadFile(siteUrl, folderServerRelativeUrl, fileName, fileContent, /** default options: { overwrite: true } */ options) { siteUrl = GetSiteUrl(siteUrl); options = options || { overwrite: true }; folderServerRelativeUrl = makeServerRelativeUrl(folderServerRelativeUrl, siteUrl); if (options && options.autoRename) { //get all files from this folder and find the next available name let files = await GetFolderFiles(siteUrl, folderServerRelativeUrl); let fileNames = files.map(f => f.Name.toLowerCase()); let counter = 0; let originalName = fileName.split('.'); originalName.splice(originalName.length - 1, 0, counter.toString()); while (fileNames.includes(fileName.toLowerCase())) { counter++; originalName[originalName.length - 2] = counter.toString(); fileName = originalName.join('.'); } } return GetJson(`${GetRestBaseUrl(siteUrl)}/Web/getFolderByServerRelativeUrl(serverRelativeUrl='${folderServerRelativeUrl}')/files/add(url='${fileName}'${options.overwrite ? ',overwrite=true' : ''})?$expand=ListItemAllFields`, fileContent, { method: 'POST', spWebUrl: siteUrl, allowCache: false, postCacheKey: null }) //Issue 6657 force set "POST" since we might send empty string as the value .then(r => { return r.d; }) .catch(() => { return { Exists: false }; }); } export async function PublishFile(siteUrl, fileUrl, comment = "") { let result = await _moderateFile(siteUrl, fileUrl, "publish", comment); return result; } export async function UnpublishFile(siteUrl, fileUrl, comment = "") { let result = await _moderateFile(siteUrl, fileUrl, "unpublish", comment); return result; } export async function ApproveFile(siteUrl, fileUrl, comment = "") { siteUrl = GetSiteUrl(siteUrl); let result = await _moderateFile(siteUrl, fileUrl, "approve", comment); return result; } export async function RejectFile(siteUrl, fileUrl, comment = "") { let result = await _moderateFile(siteUrl, fileUrl, "deny", comment); return result; } async function _moderateFile(siteUrl, fileUrl, action, comment = "") { siteUrl = GetSiteUrl(siteUrl); let fileServerRelativeUrl = makeServerRelativeUrl(fileUrl, siteUrl); try { let hasComments = !isNullOrEmptyString(comment); let publishUrl = `${GetRestBaseUrl(siteUrl)}/Web/getFileByServerRelativeUrl('${fileServerRelativeUrl}')/${action}${hasComments ? `(@a1)?@a1=%27${encodeURIComponentEX(comment, { singleQuoteMultiplier: 2 })}%27` : '()'}`; let publishResult = await GetJson(publishUrl, null, { method: "POST", jsonMetadata: jsonTypes.nometadata, includeDigestInPost: true }); return !isNullOrUndefined(publishResult) && publishResult["odata.null"] === true; } catch { } return false; } export function RecycleFile(siteUrl, fileServerRelativeUrl) { siteUrl = GetSiteUrl(siteUrl); let fileRestUrl = GetFileRestUrl(siteUrl, fileServerRelativeUrl) + "/recycle()"; return GetJson(fileRestUrl, null, { method: "POST", headers: { "IF-MATCH": "*" } }) .then(r => true) .catch((e) => false); } export function DeleteFile(siteUrl, fileServerRelativeUrl) { siteUrl = GetSiteUrl(siteUrl); let fileRestUrl = GetFileRestUrl(siteUrl, fileServerRelativeUrl); return GetJson(fileRestUrl, null, { method: "POST", xHttpMethod: "DELETE" }) .then(r => true) .catch((e) => false); } /** get the REST url for the site/_api/web/getfile....() */ function GetFileRestUrl(siteUrl, fileServerRelativeUrl) { fileServerRelativeUrl = makeServerRelativeUrl(fileServerRelativeUrl, siteUrl); let fileRestUrl = `${GetRestBaseUrl(siteUrl)}/Web/getFileByServerRelativeUrl('${fileServerRelativeUrl}')`; return fileRestUrl; } export function GetFileSync(siteUrl, fileServerRelativeUrl, responseType, options) { siteUrl = GetSiteUrl(siteUrl); let restOptions = isNullOrUndefined(options) || options.cache !== "long" ? { ...shortLocalCache } : { ...longLocalCache }; if (options && options.cache === "nocache") restOptions.forceCacheUpdate = true; if (!isNullOrUndefined(responseType)) { restOptions.responseType = responseType; } let fileRestUrl = GetFileRestUrl(siteUrl, fileServerRelativeUrl); if (!restOptions.forceCacheUpdate && reloadCacheFileModifiedRecently(siteUrl, fileServerRelativeUrl)) { restOptions.forceCacheUpdate = true; } let response = GetJsonSync(`${fileRestUrl}/$value`, null, restOptions); if (response && response.success) return { Exists: true, Content: response.result }; else return { Exists: false }; } /** @deprecated use GetFileEx */ export function GetFile(siteUrl, fileServerRelativeUrl, allowCache, responseType) { return GetFileEx(siteUrl, fileServerRelativeUrl, { allowCache, responseType }); } export async function GetFileEx(siteUrl, fileServerRelativeUrl, options) { siteUrl = GetSiteUrl(siteUrl); options = options || {}; let restOptions = { ...(options.allowCache === true ? shortLocalCache : noLocalCache), forceCacheUpdate: options.allowCache !== true }; if (!isNullOrUndefined(options.responseType)) { restOptions.responseType = options.responseType; } let version = options.version; let versionPart = ""; if (isNumber(version) && version > 0 || isNotEmptyString(version)) { //this end point does not work on MSAL claims // //get content of specific version // let fileSiteRelativeUrl = fileServerRelativeUrl.slice(siteUrl.length - 1); // let versionUrl = `${siteUrl}_vti_history/${FileVersionToVersionId(options.version)}${fileSiteRelativeUrl}`; // try { // restOptions.jsonMetadata = jsonTypes.nometadata; // let versionContent = await GetJson<T>(versionUrl, undefined, restOptions); // return { Exists: isString(versionContent), Content: versionContent }; // } catch (e) { // return { Exists: false }; // } versionPart = `/versions(${FileVersionToVersionId(options.version)})/`; } let fileRestUrl = GetFileRestUrl(siteUrl, fileServerRelativeUrl); if (!restOptions.forceCacheUpdate && reloadCacheFileModifiedRecently(siteUrl, fileServerRelativeUrl)) { restOptions.forceCacheUpdate = true; } return GetJson(`${fileRestUrl}${versionPart}/$value`, null, restOptions).then(r => { return { Exists: true, Content: r }; }).catch(() => { return { Exists: false }; }); } /** get file version history olders version first with correct check in comment, does NOT include current version */ export async function GetFileVersionHistory(siteUrl, fileServerRelativeUrl, options) { siteUrl = GetSiteUrl(siteUrl); options = options || {}; let restOptions = { allowCache: options.refreshCache !== true, jsonMetadata: jsonTypes.nometadata }; let fileRestUrl = GetFileRestUrl(siteUrl, fileServerRelativeUrl); if (!restOptions.forceCacheUpdate && reloadCacheFileModifiedRecently(siteUrl, fileServerRelativeUrl)) { restOptions.forceCacheUpdate = true; } try { const result = await GetJson(`${fileRestUrl}/versions`, null, restOptions); //Created will come in as string result.value.forEach(v => v.Created = new Date(v.Created)); return result.value; } catch (e) { logger.error(GetError(e)); } } /** version: 1.5 >> version ID for history */ export function FileVersionToVersionId(version) { try { if (isNumber(version)) return version; const vSplit = version.split('.'); const major = parseInt(vSplit[0], 10); const minor = parseInt(vSplit[1], 10); let versionId = (major * 512) + minor; return versionId; } catch (e) { } return null; } var $reloadCacheFileModifiedRecentlyFlagged = []; function reloadCacheFileModifiedRecently(siteUrl, fileServerRelativeUrl) { let fileRestUrl = GetFileRestUrl(siteUrl, fileServerRelativeUrl); let key = fileRestUrl.toLowerCase(); //only flag it once, first time it is requested... if (!$reloadCacheFileModifiedRecentlyFlagged.includes(key)) { try { $reloadCacheFileModifiedRecentlyFlagged.push(key); let fileInfo = GetJsonSync(`${fileRestUrl}?$select=TimeLastModified`, null, { allowCache: true, //only allow in-memory cache for this jsonMetadata: jsonTypes.nometadata }); if (fileInfo.success && fileInfo.result) { let modified = new Date(fileInfo.result.TimeLastModified); let now = new Date(); let difference = now.getTime() - modified.getTime(); if (difference < 5 * 60 * 1000) { //file has changed in the past 5 minutes - do not allow cache on it. //happens when user uses classic app to change settings, the clear cache does not clear it on the main //site URL ( support case - Issue 778 780 & 782 ) return true; } } } catch (e) { } } return false; } export async function GetFileSize(siteUrl, fileServerRelativeUrlOrListId, itemIdOrAllowCache, allowCache) { siteUrl = GetSiteUrl(siteUrl); let requestUrl = ""; if (isNumber(itemIdOrAllowCache) || isNumeric(itemIdOrAllowCache)) { requestUrl = GetListRestUrl(siteUrl, fileServerRelativeUrlOrListId) + `/items(${itemIdOrAllowCache})/File`; } else { allowCache = itemIdOrAllowCache === true; requestUrl = GetFileRestUrl(siteUrl, fileServerRelativeUrlOrListId); } let options = { allowCache: allowCache === true, jsonMetadata: jsonTypes.nometadata }; try { let result = await GetJson(`${requestUrl}/Properties?$select=vti_x005f_filesize`, null, options); return result.vti_x005f_filesize; } catch (e) { return null; } } export async function GetListFolders(siteUrl, listIdOrTitle) { siteUrl = GetSiteUrl(siteUrl); //switched to get request with no meta data - much faster. let url = GetListRestUrl(siteUrl, listIdOrTitle) + `/items?$Select=Folder/ServerRelativeUrl,Folder/Name&$filter=FSObjType eq 1&$expand=Folder`; let results = []; try { let requestResult = (await GetJson(url, null, { allowCache: true, jsonMetadata: jsonTypes.nometadata })); if (isNotEmptyArray(requestResult && requestResult.value)) { results = requestResult.value.map(f => ({ Name: f.Folder.Name, ServerRelativeUrl: normalizeUrl(f.Folder.ServerRelativeUrl) })); } } catch (e) { //Issue 7543 throttled library with lots of items will fail so return empty array logger.error(`Could not get folders from ${listIdOrTitle}, check network for more infromation.`); } return results; } export async function GetFolder(siteUrl, folderUrl, options = {}) { options = { includeFiles: false, includeFolders: false, allowCache: true, ...options }; siteUrl = GetSiteUrl(siteUrl); try { let folderServerRelativeUrl = makeServerRelativeUrl(folderUrl, siteUrl); let restUrl = `${GetRestBaseUrl(siteUrl)}/web/getFolderByServerRelativeUrl('${encodeURIComponentEX(folderServerRelativeUrl)}')`; if (options.includeFiles === true || options.includeFolders === true) { let expand = []; if (options.includeFiles) { expand.push("Files"); } if (options.includeFolders) { expand.push("Folders"); } restUrl += `?$expand=${expand.join(",")}`; } const result = await GetJson(restUrl, null, { ...(options.allowCache ? mediumLocalCache : noLocalCache), jsonMetadata: jsonTypes.nometadata }); return result; } catch { } return null; } export async function GetFileItemId(siteUrl, fileServerRelativeUrl) { siteUrl = GetSiteUrl(siteUrl); const restUrl = `${GetRestBaseUrl(siteUrl)}/web/getFileByServerRelativeUrl('${encodeURIComponentEX(fileServerRelativeUrl)}')/ListItemAllFields/id`; const result = await GetJson(restUrl, null, { jsonMetadata: jsonTypes.nometadata }); return result.value; } export async function GetFileModerationStatus(siteUrl, fileServerRelativeUrl) { siteUrl = GetSiteUrl(siteUrl); const restUrl = `${GetRestBaseUrl(siteUrl)}/web/getFileByServerRelativeUrl('${encodeURIComponentEX(fileServerRelativeUrl)}')/ListItemAllFields/OData__ModerationStatus`; const result = await GetJson(restUrl, null, { jsonMetadata: jsonTypes.nometadata }); return result.value; } export async function GetFilePublishingStatus(siteUrl, fileServerRelativeUrl) { siteUrl = GetSiteUrl(siteUrl); const restUrl = `${GetRestBaseUrl(siteUrl)}/web/getFileByServerRelativeUrl('${encodeURIComponentEX(fileServerRelativeUrl)}')/level`; const result = await GetJson(restUrl, null, { jsonMetadata: jsonTypes.nometadata }); return result.value; } export async function GetFileItemInfo(siteUrl, fileServerRelativeUrl) { try { siteUrl = GetSiteUrl(siteUrl); const restUrl = `${GetRestBaseUrl(siteUrl)}/web/getFileByServerRelativeUrl('${encodeURIComponentEX(fileServerRelativeUrl)}')/ListItemAllFields`; const result = await GetJson(restUrl, null, { jsonMetadata: jsonTypes.verbose }); const itemId = result.d.Id; const listId = result.d.__metadata.uri.split("'")[1]; return { listId, itemId }; } catch (e) { return null; } } export async function GetFolderItemInfo(siteUrl, folderServerRelativeUrl) { try { siteUrl = GetSiteUrl(siteUrl); const restUrl = `${GetRestBaseUrl(siteUrl)}/web/getFolderByServerRelativeUrl('${encodeURIComponentEX(folderServerRelativeUrl)}')/ListItemAllFields`; const result = await GetJson(restUrl, null, { jsonMetadata: jsonTypes.verbose }); const itemId = result.d.Id; const listId = result.d.__metadata.uri.split("'")[1]; return { listId, itemId }; } catch (e) { return null; } } /** Creates a modern single app page and return its URL. if a file in that name exists, it will return one with (1) appended to it. */ export async function CreateAppPage(siteUrl, info) { //read more: //https://petelus.sharepoint.com/sites/CMSTest/_api/SitePages/Pages/CreateAppPage //https://spblog.net/post/2019/03/05/what-s-new-and-what-s-changed-in-sharepoint-online-rest-api-in-january-february-2019 function getFileServerRelativeUrl(siteRelative) { const fileRelativeUrl = makeServerRelativeUrl(`${siteUrl}${siteRelative}`); return fileRelativeUrl; } let webPartDataAsJson = info.webPartDataAsJson; if (isNullOrEmptyString(webPartDataAsJson.instanceId)) webPartDataAsJson.instanceId = newGuid(); if (isNullOrEmptyString(webPartDataAsJson.dataVersion)) webPartDataAsJson.dataVersion = "1.0"; return logger.groupAsync("CreateAppPage", async (log) => { siteUrl = GetSiteUrl(siteUrl); const restUrl = `${GetRestBaseUrl(siteUrl)}/SitePages/Pages/CreateAppPage`; const result = await GetJson(restUrl, jsonStringify({ webPartDataAsJson: jsonStringify(webPartDataAsJson) }), { method: 'POST', jsonMetadata: jsonTypes.nometadata }); log(`created page`); log(jsonStringify(result)); let fileRelativeUrl = getFileServerRelativeUrl(result.value); const fileId = await GetFileItemId(siteUrl, fileRelativeUrl); const updateRestUrl = `${GetRestBaseUrl(siteUrl)}/SitePages/Pages/UpdateAppPage`; const updateResult = await GetJson(updateRestUrl, jsonStringify({ pageId: fileId, title: info.name, webPartDataAsJson: jsonStringify(webPartDataAsJson) }), { method: 'POST', jsonMetadata: jsonTypes.nometadata }); log(`updated page`); log(jsonStringify(updateResult)); fileRelativeUrl = getFileServerRelativeUrl(updateResult.value); return fileRelativeUrl; }); } /** Move a file to a new name/url, this API allows for changing file extension as well */ export async function MoveFile(siteUrl, currentServerRelativeUrl, targetServerRelativeUrl, options) { return CopyOrMoveFile(siteUrl, currentServerRelativeUrl, targetServerRelativeUrl, "move", options); //this does NOT allow to change the file extension. only file name. // return UpdateItem(siteUrl, listIdOrTitle, itemId, { // FileLeafRef: newFileName "hello.txt" >> "hello.md" won't work. // }); } /** Copy a file to a new name/url, this API allows for changing file extension as well */ export async function CopyFile(siteUrl, currentServerRelativeUrl, targetServerRelativeUrl, options) { return CopyOrMoveFile(siteUrl, currentServerRelativeUrl, targetServerRelativeUrl, "copy", options); } async function CopyOrMoveFile(siteUrl, currentServerRelativeUrl, targetServerRelativeUrl, action, options) { try { if (options && options.autoRename) { let targetParts = targetServerRelativeUrl.split('/'); let fileName = targetParts.pop(); let targetFolderUrl = targetParts.join('/'); //get all files from this folder and find the next available name let files = await GetFolderFiles(siteUrl, targetFolderUrl); let fileNames = files.map(f => f.Name.toLowerCase()); let counter = 0; let originalName = fileName.split('.'); originalName.splice(originalName.length - 1, 0, counter.toString()); while (fileNames.includes(fileName.toLowerCase())) { counter++; originalName[originalName.length - 2] = counter.toString(); fileName = originalName.join('.'); } targetServerRelativeUrl = `${targetFolderUrl}/${fileName}`; } let url = `${GetRestBaseUrl(siteUrl)}/web/getfilebyserverrelativeurl('${currentServerRelativeUrl}')/`; if (action === "copy") { url += `copyto(strNewUrl='${targetServerRelativeUrl}',bOverwrite=${options && options.overwrite ? "true" : "false"})`; } else { url += `moveto(newurl='${targetServerRelativeUrl}',flags=${options && options.overwrite ? 1 : 0})`; } let result = await GetJson(url, undefined, { method: "POST", jsonMetadata: jsonTypes.nometadata }); logger.json(result, "CopyOrMoveFile"); return true; } catch (e) { logger.json(e, "CopyOrMoveFile"); return false; } //this does NOT allow to change the file extension. only file name. // return UpdateItem(siteUrl, listIdOrTitle, itemId, { // FileLeafRef: newFileName "hello.txt" >> "hello.md" won't work. // }); } //# sourceMappingURL=file.folder.js.map