const UB = require('@unitybase/ub-pub')
const { webDavSchemas } = require('@unitybase/cs-shared')
/**
* WebDav utils. Expect `@ub-e/webdav-model` to be in domain - it inject `uiSettings.webdav` into appInfo based
* on `customSettings.webdav` server side config. Otherwise, `isWebDavEnabled` returns `false`
*
* @example
const adminuiVue = require('@unitybase/adminui-vue')
if (
adminuiVue.webDav.isEntitySupportWebDav(this.entityName) &&
adminuiVue.webDav.canBeOpenedInApp(this.fileName)
) {
adminuiVue.webDav.openInApp({
entity: this.entityName,
attribute: this.attributeName,
ID: this.fileId
}, this.fileName)
}
* @module webDav
* @memberOf module:@unitybase/adminui-vue
*/
module.exports = {
isWebDavEnabled,
isEntitySupportWebDav,
canBeOpenedInApp,
canGetPermanentURI,
getPermanentURI,
getURI,
openInApp
}
/**
* Returns `true` in case WebDav is enabled on server-side
*
* @returns {boolean}
*/
function isWebDavEnabled () {
// uiSettings.webdav is injected into appInfo by `@ub-e/webdav-model` based on customSettings.webdav server side config
const davCfg = UB.connection.appConfig.uiSettings.adminUI.webdav
return !!(davCfg && davCfg.enabled)
}
let davEntitiesSet
/**
* Returns true in case WebDav is enabled and entity supports WebDaw
*
* @param {string} entityCode
* @returns {boolean}
*/
function isEntitySupportWebDav (entityCode) {
if (!isWebDavEnabled()) return false
if (!davEntitiesSet) {
const entitiesCollection = UB.connection.appConfig.uiSettings.adminUI.webdav.entities
davEntitiesSet = new Set(entitiesCollection.map(eCfg => eCfg.name))
}
return davEntitiesSet.has(entityCode)
}
/**
* Return `true` if file can be opened on application. For actual opening, application what handle depp links should be installed
*
* @param {string} fileName
* @returns {boolean}
*/
function canBeOpenedInApp (fileName) {
return isWebDavEnabled() && !!getSchemaByFileName(fileName)
}
/**
* Return `true` if permanent (without auth token) WebDav URI can be generated (only in case Kerberos is used for authentication).
* - if `fileName` is empty - returns `true` if authorization support permalinks
* - if `fileName` is passed - returns `true` if authorization support permalinks and file type support opening in app
*
* @param {string} [fileName]
* @returns {boolean}
*/
function canGetPermanentURI (fileName) {
if (!isWebDavEnabled()) return false
const lastAuthType = window.localStorage.getItem(UB.LDS_KEYS.LAST_AUTH_SCHEMA) || ''
return (lastAuthType === 'Negotiate') && (!fileName || canBeOpenedInApp(fileName))
}
/**
* Synchronously return permanent WebDav URI (http.....) for specified document
* - in case `canGetPermanentURI` returns `true` for this file name - returns URI
* - otherwise - returns empty string ''
*
* @param {object} instanceInfo Instance information
* @param {string} instanceInfo.entity Code of entity to retrieve from
* @param {string} instanceInfo.attribute Code of `document` type attribute for specified entity
* @param {string} instanceInfo.ID Instance ID
* @param {string} [fileName] Name of file in store (usually origName || fName)
* @returns {string}
*/
function getPermanentURI (instanceInfo, fileName) {
if (!canGetPermanentURI(fileName)) return ''
return _buildURI(instanceInfo, fileName)
}
/**
* Return WebDav URI (http.....) for specified document.
* If user authorised using Negotiate (domain) authentication - generate a permanent link, otherwise link contains auth token
* and valid until current user session is active.
*
* @param {object} instanceInfo Instance information
* @param {string} instanceInfo.entity Code of entity to retrieve from
* @param {string} instanceInfo.attribute Code of `document` type attribute for specified entity
* @param {string} instanceInfo.ID Instance ID
* @param {string} [fileName] Name of file in store (usually origName || fName)
* @param {boolean} [forceAuthToken=false] Force adding an Authorization token into URI path for non-Kerberos authorization.
* Recommended way is to use Kerberos (domain) authorization and create semantic URL without AuthToken.
* By default, if user authorised using Kerberos - semantically correct URL without token is generated and application (Office)
* will authorise request using Kerberos
* @param {boolean} [readOnly=true] Add /ro/ part into URL - UnityBase server handle such URI as readOnly request
* @returns {Promise<{uri: string, withAuthToken: boolean}>}
*/
async function getURI (instanceInfo, fileName, forceAuthToken = false, readOnly = true) {
const lastAuthType = window.localStorage.getItem(UB.LDS_KEYS.LAST_AUTH_SCHEMA) || ''
const withAuthToken = forceAuthToken || (lastAuthType !== 'Negotiate')
let OTP
if (withAuthToken) {
const session = await UB.connection.authorize()
OTP = 'otp' + session.signature()
}
return {
uri: _buildURI(instanceInfo, fileName, OTP, readOnly),
withAuthToken
}
}
/**
* Build WebDav URI with optional OTP
*
* @param {object} instanceInfo Instance information
* @param {string} instanceInfo.entity Code of entity to retrieve from
* @param {string} instanceInfo.attribute Code of `document` type attribute for specified entity
* @param {string} instanceInfo.ID Instance ID
* @param {string} [fileName] Name of file in store (usually origName || fName)
* @param {string|undefined} [OTP] If passed - wil be added to URI after provider name
* @param {boolean} [readOnly=true] Add /ro/ part into URL - UnityBase server handle such URI as readOnly request
* @return {string}
* @private
*/
function _buildURI (instanceInfo, fileName, OTP, readOnly) {
const davCfg = UB.connection.appConfig.uiSettings.adminUI.webdav
let davURI = `${window.location.origin}/${davCfg.endpoint}/`
if (OTP) {
davURI += (OTP + '/')
}
let shortFn = fileName
const MAX_FN_LEN = 99
if (shortFn.length > MAX_FN_LEN) {
// MS Office limitation is 219 characters in file path - see https://support.microsoft.com/en-us/topic/error-message-when-you-open-or-save-a-file-in-microsoft-excel-filename-is-not-valid-951229f3-dc14-980f-765e-224e4fdc7331
// URL before file name is up to 120 (in case of otp) chars, like https://server.url/folders/otp23815cdd65bccc9ff36bc9b2/doc/doc_attachment/document/3000119001487/,
// so we have ~100 characters for file name. UnityBase server limit a file name length to 100 char max.
// Since file name is "virtual" and passed to URl only for correct displaying of file name on client, we reduce length here without losing the extension
let extPos = shortFn.lastIndexOf('.')
if (extPos === -1) extPos = shortFn.length
const ext = shortFn.substring(extPos)
shortFn = shortFn.substring(0, MAX_FN_LEN - ext.length - 2) + '~1' + ext // fileNameOf100Chars.docx -> fileNameOf100C~1.docx
}
shortFn = encodeURIComponent(shortFn) // file name should be encoded to produce valid URL
const uriParts = [davCfg.provider]
if (readOnly) uriParts.push('ro')
uriParts.push(instanceInfo.entity)
uriParts.push(instanceInfo.attribute)
uriParts.push(instanceInfo.ID)
uriParts.push(shortFn)
davURI += uriParts.join('/')
return davURI
}
/**
* Open specified blobItem in desktop application, associated with file extension by redirecting
* browser to [office URI](https://docs.microsoft.com/office/client-developer/office-uri-schemes).
*
* If user authorised using Negotiate (domain) authentication - permanent is user, otherwise link contains auth token
* and valid until current user session is active.
*
* @param {object} instanceInfo Instance information
* @param {string} instanceInfo.entity Code of entity to retrieve from
* @param {string} instanceInfo.attribute Code of `document` type attribute for specified entity
* @param {string} instanceInfo.ID Instance ID
* @param {string} [fileName] Name of file in store (usually origName || fName)
* @param {object} [options={}]
* @param {boolean} [options.forEdit=false] Force app to open file in edit mode
* @returns {Promise<boolean>} Return true if permanent link is used
*/
async function openInApp (instanceInfo, fileName, options = { }) {
const schema = getSchemaByFileName(fileName)
const { uri, withAuthToken } = await getURI(instanceInfo, fileName, false, !options.forEdit)
const url = schema + (options.forEdit ? 'ofe|u|' : 'ofv|u|') + uri
window.localStorage.setItem(UB.LDS_KEYS.PREVENT_CALL_LOGOUT_ON_UNLOAD, 'true')
if (!UB.isGecko) {
document.location.href = url
} else {
// A simple "document.location.href" change would close WS connections on FF browser
// So, we need to use a trick with iframe
// Add iframe, if not exists
if (!document.querySelector('iframe.ws_keep_conn')) {
const iframe = document.createElement('iframe')
iframe.name = 'ws_keep_conn'
iframe.className = 'ws_keep_conn'
iframe.style.display = 'none'
document.body.appendChild(iframe)
}
// Add a link to the DOM and click it programmatically, and remove the link instantly
const link = document.createElement('a')
link.href = url
link.target = 'ws_keep_conn'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
return !withAuthToken
}
/**
* Return URI schema name for specified file, or '' if schema not known for file extension
*
* @param {string} fileName
* @returns {string}
*/
function getSchemaByFileName (fileName) {
if (!fileName) return ''
const dotIdx = fileName.lastIndexOf('.')
if (dotIdx === -1) return ''
const ext = fileName.substring(dotIdx).toLowerCase()
return webDavSchemas.FILE_EXT2SCHEMA[ext] || ''
}