@kwiz/common
Version:
KWIZ common utilities and helpers for M365 platform
503 lines • 23.5 kB
JavaScript
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