diff --git a/blocks/browse/da-browse/da-browse.js b/blocks/browse/da-browse/da-browse.js index 4b9ac5b5..ca47b03d 100644 --- a/blocks/browse/da-browse/da-browse.js +++ b/blocks/browse/da-browse/da-browse.js @@ -1,6 +1,7 @@ import { LitElement, html, nothing } from 'da-lit'; -import { DA_ORIGIN } from '../../shared/constants.js'; -import { daFetch, getFirstSheet } from '../../shared/utils.js'; +import { DA_HLX } from '../../shared/constants.js'; +import { getFirstSheet } from '../../shared/utils.js'; +import { daApi } from '../../shared/da-api.js'; import { getNx, sanitizePathParts } from '../../../scripts/utils.js'; // Components @@ -82,8 +83,12 @@ export default class DaBrowse extends LitElement { async getEditor(reFetch) { const DEF_EDIT = '/edit#'; + if (DA_HLX) { // TODO handle editor path for hlx6 + return DEF_EDIT; + } + if (reFetch) { - const resp = await daFetch(`${DA_ORIGIN}/config/${this.details.owner}/`); + const resp = await daApi.getConfig(`/${this.details.owner}/`); if (!resp.ok) return DEF_EDIT; const json = await resp.json(); diff --git a/blocks/browse/da-list-item/da-list-item.js b/blocks/browse/da-list-item/da-list-item.js index f40a50eb..a06fa9c3 100644 --- a/blocks/browse/da-list-item/da-list-item.js +++ b/blocks/browse/da-list-item/da-list-item.js @@ -1,6 +1,6 @@ import { LitElement, html, nothing, until } from 'da-lit'; -import { DA_ORIGIN } from '../../shared/constants.js'; -import { daFetch, aemAdmin } from '../../shared/utils.js'; +import { aemAdmin } from '../../shared/utils.js'; +import { daApi } from '../../shared/da-api.js'; import { getNx } from '../../../scripts/utils.js'; import getEditPath from '../shared.js'; import { formatDate } from '../../edit/da-versions/helpers.js'; @@ -73,7 +73,7 @@ export default class DaListItem extends LitElement { } async updateDAStatus() { - const resp = await daFetch(`${DA_ORIGIN}/versionlist${this.path}`); + const resp = await daApi.getVersionList(this.path); if (!resp.ok) return; const json = await resp.json(); if (json.length === 0) { @@ -131,10 +131,6 @@ export default class DaListItem extends LitElement { this._preview = null; this._live = null; - const formData = new FormData(); - formData.append('destination', newPath); - const opts = { body: formData, method: 'POST' }; - this.name = newName; this.path = newPath; this.rename = false; @@ -142,7 +138,7 @@ export default class DaListItem extends LitElement { this.date = Date.now(); const showStatus = setTimeout(() => { this.setStatus('Renaming', 'Please be patient. Renaming items with many children can take time.'); }, 5000); - const resp = await daFetch(`${DA_ORIGIN}/move${oldPath}`, opts); + const resp = await daApi.move(oldPath, newPath); if (resp.status === 204) { clearTimeout(showStatus); @@ -204,7 +200,7 @@ export default class DaListItem extends LitElement { let externalUrlPromise; if (this.ext === 'link') { path = nothing; - externalUrlPromise = daFetch(`${DA_ORIGIN}/source${this.path}`) + externalUrlPromise = daApi.getSource(this.path) .then((response) => response.json()) .then((data) => data.externalUrl); } @@ -216,8 +212,8 @@ export default class DaListItem extends LitElement { ` : html` + `} -
${this.name}
${this.ext === 'link' ? nothing : this.renderDate()}
`; diff --git a/blocks/browse/da-list/da-list.js b/blocks/browse/da-list/da-list.js index c57e006f..e99a7c2f 100644 --- a/blocks/browse/da-list/da-list.js +++ b/blocks/browse/da-list/da-list.js @@ -1,7 +1,8 @@ import { LitElement, html, repeat, nothing } from 'da-lit'; -import { DA_ORIGIN } from '../../shared/constants.js'; +import { DA_HLX } from '../../shared/constants.js'; import { getNx, sanitizePathParts } from '../../../scripts/utils.js'; -import { daFetch, aemAdmin } from '../../shared/utils.js'; +import { aemAdmin } from '../../shared/utils.js'; +import { daApi } from '../../shared/da-api.js'; import '../da-list-item/da-list-item.js'; @@ -97,12 +98,33 @@ export default class DaList extends LitElement { this.dispatchEvent(event); } + transformList(json) { + if (!DA_HLX) return json; + + /* + * name without extension + * ext is the extension + * lastModified is last-modified in millis + * path is full plus original name with extension + */ + + const transformed = []; + for (const item of json) { + const [name, ext] = item.name.split('.'); + const lastModified = new Date(item['last-modified']).getTime(); + const path = `${this.fullpath}/${item.name}`; + + transformed.push({ name, ext, lastModified, path }); + } + return transformed; + } + async getList() { try { - const resp = await daFetch(`${DA_ORIGIN}/list${this.fullpath}`); + const resp = await daApi.getList(this.fullpath); if (resp.permissions) this.handlePermissions(resp.permissions); const json = await resp.json(); - return json; + return this.transformList(json); } catch { this._emptyMessage = 'Not permitted'; return []; @@ -212,24 +234,23 @@ export default class DaList extends LitElement { const moveToTrash = api === 'move' && !item.path.includes('/.trash/') && item.destination.includes('/.trash/'); try { - while (continuation) { - let body; - - if (type !== 'delete') { - body = new FormData(); - body.append('destination', item.destination); - if (continuationToken) body.append('continuation-token', continuationToken); - } - - const opts = { method, body }; - const resp = await daFetch(`${DA_ORIGIN}/${api}${item.path}`, opts); - if (resp.status === 204) { - continuation = false; - break; + if (type === 'delete') { + const resp = await daApi.deleteSource(item.path); + if (resp.status !== 204) throw new Error('Could not delete'); + } else { + while (continuation) { + const resp = await (api === 'move' + ? daApi.move(item.path, item.destination, continuationToken) + : daApi.copy(item.path, item.destination, continuationToken)); + + if (resp.status === 204) { + continuation = false; + break; + } + const json = await resp.json(); + ({ continuationToken } = json); + if (!continuationToken) continuation = false; } - const json = await resp.json(); - ({ continuationToken } = json); - if (!continuationToken) continuation = false; } item.isChecked = false; diff --git a/blocks/browse/da-list/helpers/utils.js b/blocks/browse/da-list/helpers/utils.js index 6a8f59e1..9ab01fe2 100644 --- a/blocks/browse/da-list/helpers/utils.js +++ b/blocks/browse/da-list/helpers/utils.js @@ -1,6 +1,6 @@ -import { SUPPORTED_FILES, DA_ORIGIN } from '../../../shared/constants.js'; +import { SUPPORTED_FILES } from '../../../shared/constants.js'; import { sanitizePath, sanitizePathParts } from '../../../../scripts/utils.js'; -import { daFetch } from '../../../shared/utils.js'; +import { daApi } from '../../../shared/da-api.js'; const MAX_DEPTH = 1000; @@ -84,14 +84,11 @@ export async function getFullEntryList(entries) { export async function handleUpload(list, fullpath, file) { const { data, path } = file; - const formData = new FormData(); - formData.append('data', data); - const opts = { method: 'POST', body: formData }; const sanitizedPath = sanitizePath(path); const postpath = `${fullpath}${sanitizedPath}`; try { - await daFetch(`${DA_ORIGIN}/source${postpath}`, opts); + await daApi.saveSource(postpath, { blob: data, method: 'POST' }); file.imported = true; const [displayName] = sanitizedPath.split('/').slice(1); diff --git a/blocks/browse/da-new/da-new.js b/blocks/browse/da-new/da-new.js index 7b027675..4a7a05f2 100644 --- a/blocks/browse/da-new/da-new.js +++ b/blocks/browse/da-new/da-new.js @@ -2,6 +2,7 @@ import { LitElement, html } from 'da-lit'; import { saveToDa } from '../../shared/utils.js'; import { getNx } from '../../../scripts/utils.js'; import getEditPath from '../shared.js'; +import { daApi } from '../../shared/da-api.js'; // Styles & Icons const { default: getStyle } = await import(`${getNx()}/utils/styles.js`); @@ -68,6 +69,7 @@ export default class DaNew extends LitElement { let ext; let formData; + let method = 'PUT'; switch (this._createType) { case 'document': ext = 'html'; @@ -83,6 +85,12 @@ export default class DaNew extends LitElement { new Blob([JSON.stringify({ externalUrl: this._externalUrl })], { type: 'application/json' }), ); break; + case 'folder': + if (daApi.isHlx) { + this._createName += '/'; + method = 'POST'; + } + break; default: break; } @@ -92,7 +100,7 @@ export default class DaNew extends LitElement { if (ext && ext !== 'link') { window.location = editPath; } else { - await saveToDa({ path, formData }); + await saveToDa({ path, formData, method }); const item = { name: this._createName, path }; if (ext) item.ext = ext; this.sendNewItem(item); diff --git a/blocks/browse/da-search/da-search.js b/blocks/browse/da-search/da-search.js index 05aa2704..b20e67fa 100644 --- a/blocks/browse/da-search/da-search.js +++ b/blocks/browse/da-search/da-search.js @@ -1,7 +1,6 @@ import { LitElement, html, nothing } from 'da-lit'; -import { DA_ORIGIN } from '../../shared/constants.js'; import { getNx } from '../../../scripts/utils.js'; -import { daFetch } from '../../shared/utils.js'; +import { daApi } from '../../shared/da-api.js'; const { crawl, Queue } = await import(`${getNx()}/public/utils/tree.js`); @@ -69,7 +68,7 @@ export default class DaSearch extends LitElement { let match; try { - const resp = await daFetch(`${DA_ORIGIN}/source${file.path}`); + const resp = await daApi.getSource(file.path); const text = await resp.text(); // Log empty files // eslint-disable-next-line no-console @@ -163,14 +162,11 @@ export default class DaSearch extends LitElement { let retryCount = prevRetry; const getFile = async () => { - const getResp = await daFetch(`${DA_ORIGIN}/source${file.path}`); + const getResp = await daApi.getSource(file.path); const text = await getResp.text(); const replacedText = text.replaceAll(this._term, replace.value); const blob = new Blob([replacedText], { type: 'text/html' }); - const formData = new FormData(); - formData.append('data', blob); - const opts = { method: 'PUT', body: formData }; - const postResp = await daFetch(`${DA_ORIGIN}/source${file.path}`, opts); + const postResp = await daApi.saveSource(file.path, { blob }); if (!postResp.ok) return { error: 'Error saving file' }; this._matches += 1; return file; diff --git a/blocks/edit/da-assets/da-assets.js b/blocks/edit/da-assets/da-assets.js index 72781bd8..dfdb7cf5 100644 --- a/blocks/edit/da-assets/da-assets.js +++ b/blocks/edit/da-assets/da-assets.js @@ -1,7 +1,7 @@ import { DOMParser as proseDOMParser, Fragment } from 'da-y-wrapper'; import { getNx } from '../../../scripts/utils.js'; -import { DA_ORIGIN } from '../../shared/constants.js'; import { daFetch, getFirstSheet } from '../../shared/utils.js'; +import { daApi } from '../../shared/da-api.js'; import getPathDetails from '../../shared/pathDetails.js'; const { loadStyle } = await import(`${getNx()}/scripts/nexter.js`); @@ -15,7 +15,7 @@ const CONFS = {}; async function fetchConf(path) { if (CONFS[path]) return CONFS[path]; - const resp = await daFetch(`${DA_ORIGIN}/config${path}`); + const resp = await daApi.getConfig(path); if (!resp.ok) return null; fullConfJsons[path] = await resp.json(); @@ -37,7 +37,7 @@ async function fetchValue(path, key) { } function constructConfigPaths(owner, repo) { - return [`/${owner}/${repo}/`, `/${owner}/`]; + return [`/${owner}/${repo}/`]; } // Note: this is called externally to determine if the button should be visible. diff --git a/blocks/edit/da-content/helpers/index.js b/blocks/edit/da-content/helpers/index.js index 0224bbad..d8ee8f01 100644 --- a/blocks/edit/da-content/helpers/index.js +++ b/blocks/edit/da-content/helpers/index.js @@ -1,8 +1,7 @@ -import { DA_ORIGIN } from '../../../shared/constants.js'; -import { daFetch } from '../../../shared/utils.js'; +import { daApi } from '../../../shared/da-api.js'; async function getConfSheet(org) { - const resp = await daFetch(`${DA_ORIGIN}/config/${org}/`); + const resp = await daApi.getConfig(`/${org}/`); if (!resp.ok) return null; const json = await resp.json(); return json?.data?.data || json?.data; diff --git a/blocks/edit/da-library/helpers/helpers.js b/blocks/edit/da-library/helpers/helpers.js index 0f98d176..c558aa93 100644 --- a/blocks/edit/da-library/helpers/helpers.js +++ b/blocks/edit/da-library/helpers/helpers.js @@ -1,13 +1,12 @@ // eslint-disable-next-line import/no-unresolved import { DOMParser } from 'da-y-wrapper'; -import { getDaAdmin } from '../../../shared/constants.js'; import getPathDetails from '../../../shared/pathDetails.js'; import { daFetch, getFirstSheet } from '../../../shared/utils.js'; +import { daApi } from '../../../shared/da-api.js'; import { getConfKey, openAssets } from '../../da-assets/da-assets.js'; import { fetchKeyAutocompleteData } from '../../prose/plugins/slashMenu/keyAutocomplete.js'; import { sanitizeName } from '../../../../scripts/utils.js'; -const DA_ORIGIN = getDaAdmin(); const REPLACE_CONTENT = ''; const DA_CONFIG = '/.da/config.json'; const DA_PLUGINS = [ @@ -68,7 +67,7 @@ export async function getItems(sources, listType, format) { } async function getDaLibraries(owner, repo) { - const resp = await daFetch(`${DA_ORIGIN}/source/${owner}/${repo}${DA_CONFIG}`); + const resp = await daApi.getSource(`/${owner}/${repo}${DA_CONFIG}`); if (!resp.ok) return []; const json = await resp.json(); @@ -159,7 +158,7 @@ function calculateSources(org, repo, sheetPath) { } async function getConfigLibraries(org, repo) { - const resp = await daFetch(`${DA_ORIGIN}/config/${org}/${repo}/`); + const resp = await daApi.getConfig(`/${org}/${repo}/`); if (!resp.ok) return null; const { library } = await resp.json(); if (!library) return null; @@ -256,6 +255,25 @@ export function getPreviewUrl(previewUrl) { const [, , org, site, ...split] = url.pathname.split('/'); return `https://main--${site}--${org}.aem.page/${split.join('/')}`; } + if (url.origin === daApi.origin) { + if (daApi.isHlx) { + // HLX6: /org/sites/repo/source/path... + // Assuming previewUrl is sourceUrl. + // If path is /org/sites/repo/source/rest + // Site is repo. Org is org. + const parts = url.pathname.split('/'); + // parts: ['', org, 'sites', repo, 'source', ...rest] + if (parts[2] === 'sites' && parts[4] === 'source') { + const org = parts[1]; + const repo = parts[3]; + const rest = parts.slice(5).join('/'); + return `https://main--${repo}--${org}.aem.page/${rest}`; + } + } else { + const [, , org, site, ...split] = url.pathname.split('/'); + return `https://main--${site}--${org}.aem.page/${split.join('/')}`; + } + } } catch { return false; } @@ -275,7 +293,13 @@ export function getEdsUrlVars(url) { const [, org, site] = urlObj.pathname.split('/'); return [org, site, 'main']; } - if (urlObj.origin.includes('admin.da.live')) { + if (urlObj.origin.includes('admin.da.live') || urlObj.origin === daApi.origin) { + if (daApi.isHlx && urlObj.origin === daApi.origin) { + const parts = urlObj.pathname.split('/'); + if (parts[2] === 'sites' && parts[4] === 'source') { + return [parts[1], parts[3], 'main']; + } + } const [, , org, site] = urlObj.pathname.split('/'); return [org, site, 'main']; } diff --git a/blocks/edit/da-title/da-title.js b/blocks/edit/da-title/da-title.js index 6a3ecc0b..051179a8 100644 --- a/blocks/edit/da-title/da-title.js +++ b/blocks/edit/da-title/da-title.js @@ -7,8 +7,8 @@ import { saveDaVersion, getCdnConfig, } from '../utils/helpers.js'; -import { DA_ORIGIN } from '../../shared/constants.js'; import { daFetch, getFirstSheet } from '../../shared/utils.js'; +import { daApi } from '../../shared/da-api.js'; import inlinesvg from '../../shared/inlinesvg.js'; import getSheet from '../../shared/sheet.js'; @@ -66,6 +66,7 @@ export default class DaTitle extends LitElement { } handleError(json, action, icon) { + // eslint-disable-next-line no-console console.log('handleError', json, action, icon); this._status = { ...json.error, action }; icon.classList.remove('is-sending'); @@ -172,8 +173,8 @@ export default class DaTitle extends LitElement { .catch(() => []); const [org, site] = await Promise.all([ - fetchSingleConfig(`${DA_ORIGIN}/config/${owner}`), - fetchSingleConfig(`${DA_ORIGIN}/config/${owner}/${repo}`), + fetchSingleConfig(daApi.getConfigUrl(`/${owner}`)), + fetchSingleConfig(daApi.getConfigUrl(`/${owner}/${repo}`)), ]); this.config = { org, site }; return this.config; diff --git a/blocks/edit/da-versions/da-versions.js b/blocks/edit/da-versions/da-versions.js index f2195a1a..3f8dcd90 100644 --- a/blocks/edit/da-versions/da-versions.js +++ b/blocks/edit/da-versions/da-versions.js @@ -1,8 +1,7 @@ import { LitElement, html, nothing } from 'da-lit'; import getSheet from '../../shared/sheet.js'; -import { DA_ORIGIN } from '../../shared/constants.js'; +import { daApi } from '../../shared/da-api.js'; import { formatDate, formatVersions } from './helpers.js'; -import { daFetch } from '../../shared/utils.js'; const sheet = await getSheet('/blocks/edit/da-versions/da-versions.css'); @@ -23,7 +22,7 @@ export default class DaVersions extends LitElement { async getVersions() { this._loading = true; this._versions = null; - const resp = await daFetch(`${DA_ORIGIN}/versionlist${this.path}`); + const resp = await daApi.getVersionList(this.path); if (!resp.ok) { this._loading = false; return; @@ -49,7 +48,7 @@ export default class DaVersions extends LitElement { if (!entryEl.classList.contains('is-open')) { entryEl.classList.toggle('is-open'); } - const detail = { url: `${DA_ORIGIN}${entry.url}` }; + const detail = { url: `${daApi.origin}${entry.url}` }; const opts = { detail, bubbles: true, composed: true }; const event = new CustomEvent('preview', opts); this.dispatchEvent(event); @@ -67,7 +66,7 @@ export default class DaVersions extends LitElement { const opts = { method: 'POST' }; if (entry.label) opts.body = JSON.stringify({ label: entry.label }); - const res = await daFetch(`${DA_ORIGIN}/versionsource${this.path}`, opts); + const res = await daApi.getVersionSource(this.path, opts); if (res.status !== 201) return; this._newVersion = null; diff --git a/blocks/edit/prose/index.js b/blocks/edit/prose/index.js index 9af37f1e..96d9d6c6 100644 --- a/blocks/edit/prose/index.js +++ b/blocks/edit/prose/index.js @@ -31,7 +31,8 @@ import linkConverter from './plugins/linkConverter.js'; import linkTextSync from './plugins/linkTextSync.js'; import sectionPasteHandler from './plugins/sectionPasteHandler.js'; import base64Uploader from './plugins/base64uploader.js'; -import { COLLAB_ORIGIN, DA_ORIGIN } from '../../shared/constants.js'; +import { COLLAB_ORIGIN } from '../../shared/constants.js'; +import { daApi } from '../../shared/da-api.js'; import toggleLibrary from '../da-library/da-library.js'; import { checkDoc } from '../edit.js'; import { debounce, initDaMetadata } from '../utils/helpers.js'; @@ -297,7 +298,7 @@ export default function initProse({ path, permissions }) { const ydoc = new Y.Doc(); const server = COLLAB_ORIGIN; - const roomName = `${DA_ORIGIN}${new URL(path).pathname}`; + const roomName = `${daApi.origin}${new URL(path).pathname}`; const opts = { protocols: ['yjs'] }; diff --git a/blocks/edit/prose/plugins/base64uploader.js b/blocks/edit/prose/plugins/base64uploader.js index f9865893..0c999126 100644 --- a/blocks/edit/prose/plugins/base64uploader.js +++ b/blocks/edit/prose/plugins/base64uploader.js @@ -1,7 +1,7 @@ import { Plugin } from 'da-y-wrapper'; import getPathDetails from '../../../shared/pathDetails.js'; -import { daFetch } from '../../../shared/utils.js'; -import { DA_ORIGIN, CON_ORIGIN } from '../../../shared/constants.js'; +import { daApi } from '../../../shared/da-api.js'; +import { CON_ORIGIN } from '../../../shared/constants.js'; const FPO_IMG_URL = '/blocks/edit/img/fpo.svg'; @@ -45,9 +45,7 @@ export default function base64Uploader() { uploadPromises.push((async () => { const resp = await fetch(src); const blob = await resp.blob(); - const body = new FormData(); - body.append('data', blob); - await daFetch(`${DA_ORIGIN}/source${path}`, { body, method: 'POST' }); + await daApi.saveSource(path, { blob, method: 'POST' }); })()); }); diff --git a/blocks/edit/utils/helpers.js b/blocks/edit/utils/helpers.js index 450a2ad2..5933deaa 100644 --- a/blocks/edit/utils/helpers.js +++ b/blocks/edit/utils/helpers.js @@ -1,7 +1,8 @@ -import { AEM_ORIGIN, DA_ORIGIN } from '../../shared/constants.js'; +import { AEM_ORIGIN } from '../../shared/constants.js'; import { sanitizePathParts } from '../../../../scripts/utils.js'; import prose2aem from '../../shared/prose2aem.js'; import { daFetch } from '../../shared/utils.js'; +import { daApi } from '../../shared/da-api.js'; export function isURL(text) { try { @@ -205,11 +206,7 @@ async function saveHtml(fullPath) { const html = prose2aem(editor, false); const blob = new Blob([html], { type: 'text/html' }); - const formData = new FormData(); - formData.append('data', blob); - - const opts = { method: 'PUT', body: formData }; - return daFetch(fullPath, opts); + return daApi.saveSource(fullPath, { blob }); } function formatSheetData(jData) { @@ -295,36 +292,27 @@ export function convertSheets(sheets) { async function saveJson(fullPath, sheets, jsonToSave, dataType = 'blob') { const json = jsonToSave || convertSheets(sheets); - const formData = new FormData(); - - if (dataType === 'blob') { - const blob = new Blob([JSON.stringify(json)], { type: 'application/json' }); - formData.append('data', blob); - } - - if (dataType === 'config') { - formData.append('config', JSON.stringify(json)); - } + const blob = new Blob([JSON.stringify(json)], { type: 'application/json' }); + const props = dataType === 'config' ? { config: json } : undefined; - const opts = { method: 'PUT', body: formData }; - return daFetch(fullPath, opts); + return daApi.saveSource(fullPath, { blob, props }); } export function saveToDa(pathname, sheet) { const suffix = sheet ? '.json' : '.html'; - const fullPath = `${DA_ORIGIN}/source${pathname}${suffix}`; + + const fullPath = `${pathname}${suffix}`; if (!sheet) return saveHtml(fullPath); return saveJson(fullPath, sheet); } export function saveDaConfig(pathname, sheet) { - const fullPath = `${DA_ORIGIN}/config${pathname}`; - return saveJson(fullPath, sheet, null, 'config'); + return daApi.saveSource(`${pathname}`, { blob: new Blob([JSON.stringify(sheet)], { type: 'application/json' }), props: { config: sheet } }); } export async function saveDaVersion(pathname, ext = 'html') { - const fullPath = `${DA_ORIGIN}/versionsource${pathname}.${ext}`; + const fullPath = `${pathname}.${ext}`; const opts = { method: 'POST', @@ -332,7 +320,8 @@ export async function saveDaVersion(pathname, ext = 'html') { }; try { - await daFetch(fullPath, opts); + // eslint-disable-next-line no-undef + await daApi.getVersionSource(fullPath, opts); } catch { // eslint-disable-next-line no-console console.log('Error creating auto version on publish.'); @@ -364,8 +353,8 @@ async function getRoleRequestDetails(action) { export async function requestRole(org, site, action) { let json = JSON.parse(AEM_PERMISSION_TPL); - const fullpath = `${DA_ORIGIN}/source/${org}/${site}/.da/aem-permission-requests.json`; - const resp = await daFetch(fullpath); + const fullpath = `/${org}/${site}/.da/aem-permission-requests.json`; + const resp = await daApi.getSource(fullpath); if (resp.ok) { json = await resp.json(); } diff --git a/blocks/shared/api/da-admin.js b/blocks/shared/api/da-admin.js new file mode 100644 index 00000000..23e8f393 --- /dev/null +++ b/blocks/shared/api/da-admin.js @@ -0,0 +1,84 @@ +import { daFetch } from '../fetch.js'; + +export default class DaAdminApi { + constructor(origin) { + this.origin = origin; + this.isHlx = false; + } + + getSourceUrl(path) { + return `${this.origin}/source${path}`; + } + + getConfigUrl(path) { + return `${this.origin}/config${path}`; + } + + getListUrl(path) { + return `${this.origin}/list${path}`; + } + + getVersionListUrl(path) { + return `${this.origin}/versionlist${path}`; + } + + getVersionSourceUrl(path) { + return `${this.origin}/versionsource${path}`; + } + + async getSource(path) { + return daFetch(this.getSourceUrl(path)); + } + + async deleteSource(path) { + return daFetch(this.getSourceUrl(path), { method: 'DELETE' }); + } + + async saveSource(path, { formData, blob, props, method = 'PUT' } = {}) { + const opts = { method }; + const form = formData || new FormData(); + if (blob || props) { + if (blob) form.append('data', blob); + if (props) form.append('props', JSON.stringify(props)); + } + if ([...form.keys()].length) opts.body = form; + + const daResp = await daFetch(this.getSourceUrl(path), opts); + if (!daResp.ok) return undefined; + return daResp; + } + + async getConfig(path) { + return daFetch(this.getConfigUrl(path)); + } + + async getList(path) { + return daFetch(this.getListUrl(path)); + } + + async getVersionList(path) { + return daFetch(this.getVersionListUrl(path)); + } + + async getVersionSource(path) { + return daFetch(this.getVersionSourceUrl(path)); + } + + async move(path, destination, continuationToken) { + const body = new FormData(); + body.append('destination', destination); + if (continuationToken) body.append('continuation-token', continuationToken); + + const url = `${this.origin}/move${path}`; + return daFetch(url, { method: 'POST', body }); + } + + async copy(path, destination, continuationToken) { + const body = new FormData(); + body.append('destination', destination); + if (continuationToken) body.append('continuation-token', continuationToken); + + const url = `${this.origin}/copy${path}`; + return daFetch(url, { method: 'POST', body }); + } +} diff --git a/blocks/shared/api/da-hlx6.js b/blocks/shared/api/da-hlx6.js new file mode 100644 index 00000000..7ef1fe8e --- /dev/null +++ b/blocks/shared/api/da-hlx6.js @@ -0,0 +1,99 @@ +import { daFetch } from '../fetch.js'; + +const getPathParts = (path) => { + const parts = path.split('/').filter((p) => p); + return { + org: parts[0], + repo: parts[1], + rest: parts.slice(2).join('/'), + }; +}; + +export default class DaHlx6Api { + constructor(origin) { + this.origin = origin; + this.isHlx = true; + } + + getSourceUrl(path) { + const { org, repo, rest } = getPathParts(path); + if (!repo) return `${this.origin}/source${path}`; + return `${this.origin}/${org}/sites/${repo}/source/${rest}`; + } + + getConfigUrl(path) { + const { org, repo } = getPathParts(path); + + // TODO: migrate to api.aem.live when ready + // cannot work as is because auth... not the same token is required + // https://admin.hlx.page/config/kptdobe/sites/daplayground.json + return `https://admin.hlx.page/config/${org}/sites/${repo}.json`; + + // return `${this.origin}/${org}/sites/${repo}/config/`; + } + + getListUrl(path) { + const { org, repo, rest } = getPathParts(path); + if (!repo) return `${this.origin}/list${path}`; + // HLX6 uses the source endpoint for listing as well (if it's a folder) + return `${this.origin}/${org}/sites/${repo}/source/${rest}`; + } + + getVersionListUrl(path) { + return `${this.origin}/versionlist${path}`; + } + + getVersionSourceUrl(path) { + return `${this.origin}/versionsource${path}`; + } + + async getSource(path) { + return daFetch(this.getSourceUrl(path)); + } + + async deleteSource(path) { + return daFetch(this.getSourceUrl(path), { method: 'DELETE' }); + } + + async saveSource(path, { blob, method = 'PUT' } = {}) { + const opts = { method, body: blob }; + const daResp = await daFetch(this.getSourceUrl(path), opts); + if (!daResp.ok) return undefined; + return daResp; + } + + async getConfig(path) { + return daFetch(this.getConfigUrl(path)); + } + + async getList(path) { + return daFetch(this.getListUrl(path)); + } + + async getVersionList(path) { + return daFetch(this.getVersionListUrl(path)); + } + + async getVersionSource(path) { + return daFetch(this.getVersionSourceUrl(path)); + } + + async move(path, destination, continuationToken) { + const body = new FormData(); + body.append('destination', destination); + if (continuationToken) body.append('continuation-token', continuationToken); + + // Assuming HLX6 follows similar pattern or we fallback to what was generated before + const url = `${this.origin}/move${path}`; + return daFetch(url, { method: 'POST', body }); + } + + async copy(path, destination, continuationToken) { + const body = new FormData(); + body.append('destination', destination); + if (continuationToken) body.append('continuation-token', continuationToken); + + const url = `${this.origin}/copy${path}`; + return daFetch(url, { method: 'POST', body }); + } +} diff --git a/blocks/shared/constants.js b/blocks/shared/constants.js index 6f6bdc09..3cb4eb23 100644 --- a/blocks/shared/constants.js +++ b/blocks/shared/constants.js @@ -15,12 +15,14 @@ export const SUPPORTED_FILES = { const DA_ADMIN_ENVS = { local: 'http://localhost:8787', + hlx6: 'https://api.aem.live', stage: 'https://stage-admin.da.live', prod: 'https://admin.da.live', }; const DA_COLLAB_ENVS = { local: 'ws://localhost:4711', + hlx6: 'wss://collab-hlx6.da.live', stage: 'wss://stage-collab.da.live', prod: 'wss://collab.da.live', }; @@ -60,7 +62,12 @@ export const getDaAdmin = (() => { })(); export const DA_ORIGIN = (() => getDaEnv(window.location, 'da-admin', DA_ADMIN_ENVS))(); +export const DA_HLX = DA_ADMIN_ENVS.hlx6 === DA_ORIGIN; export const COLLAB_ORIGIN = (() => getDaEnv(window.location, 'da-collab', DA_COLLAB_ENVS))(); + +export const DA_ORIGINS = ['https://da.live', 'https://da.page', 'https://admin.da.live', 'https://admin.da.page', 'https://stage-admin.da.live', 'https://content.da.live', 'https://stage-content.da.live', 'http://localhost:8787']; +export const AEM_ORIGINS = ['https://admin.hlx.page', 'https://admin.aem.live', 'https://api.aem.live']; + export const CON_ORIGIN = (() => getDaEnv(window.location, 'da-content', DA_CONTENT_ENVS))(); export const LIVE_PREVIEW_DOMAIN = (() => getDaEnv(window.location, 'da-live-preview', DA_LIVE_PREVIEW_ENVS))(); diff --git a/blocks/shared/da-api.js b/blocks/shared/da-api.js new file mode 100644 index 00000000..da3b819d --- /dev/null +++ b/blocks/shared/da-api.js @@ -0,0 +1,8 @@ +import { DA_ORIGIN, DA_HLX } from './constants.js'; +import DaAdminApi from './api/da-admin.js'; +import DaHlx6Api from './api/da-hlx6.js'; + +const DaApi = DA_HLX ? DaHlx6Api : DaAdminApi; + +export default DaApi; +export const daApi = new DaApi(DA_ORIGIN); diff --git a/blocks/shared/fetch.js b/blocks/shared/fetch.js new file mode 100644 index 00000000..ad37314a --- /dev/null +++ b/blocks/shared/fetch.js @@ -0,0 +1,73 @@ +import { DA_ORIGINS, AEM_ORIGINS } from './constants.js'; + +const { getNx } = await import('../../scripts/utils.js'); +const ALLOWED_TOKEN = [...DA_ORIGINS, ...AEM_ORIGINS]; + +let imsDetails; + +export async function initIms() { + if (imsDetails) return imsDetails; + const { loadIms } = await import(`${getNx()}/utils/ims.js`); + + try { + imsDetails = await loadIms(); + return imsDetails; + } catch { + return null; + } +} + +export const daFetch = async (url, opts = {}) => { + opts.headers = opts.headers || {}; + let accessToken; + if (localStorage.getItem('nx-ims')) { + ({ accessToken } = await initIms()); + const canToken = ALLOWED_TOKEN.some((origin) => new URL(url).origin === origin); + if (accessToken && canToken) { + opts.headers.Authorization = `Bearer ${accessToken.token}`; + if (AEM_ORIGINS.some((origin) => new URL(url).origin === origin)) { + opts.headers['x-content-source-authorization'] = `Bearer ${accessToken.token}`; + } + } + } + const resp = await fetch(url, opts); + if (resp.status === 401 && opts.noRedirect !== true) { + // Only attempt sign-in if the request is for DA. + if (DA_ORIGINS.some((origin) => url.startsWith(origin))) { + // If the user has an access token, but are not permitted, redirect them to not found. + if (accessToken) { + // eslint-disable-next-line no-console + console.warn('You see the 404 page because you have no access to this page', url); + window.location = `${window.location.origin}/not-found`; + return { ok: false }; + } + // eslint-disable-next-line no-console + console.warn('You need to sign in because you are not authorized to access this page', url); + const { loadIms, handleSignIn } = await import(`${getNx()}/utils/ims.js`); + await loadIms(); + handleSignIn(); + } + } + + // TODO: Properly support 403 - DA Admin sometimes gives 401s and sometimes 403s. + if (resp.status === 403) { + return resp; + } + + // If child actions header is present, use it. + // This is a hint as to what can be done with the children. + if (resp.headers?.get('x-da-child-actions')) { + resp.permissions = resp.headers.get('x-da-child-actions').split('=').pop().split(','); + return resp; + } + + // Use the self actions hint if child actions are not present. + if (resp.headers?.get('x-da-actions')) { + resp.permissions = resp.headers?.get('x-da-actions')?.split('=').pop().split(','); + return resp; + } + + // Support legacy admin.role.all + resp.permissions = ['read', 'write']; + return resp; +}; diff --git a/blocks/shared/pathDetails.js b/blocks/shared/pathDetails.js index a95193f3..61ee4a97 100644 --- a/blocks/shared/pathDetails.js +++ b/blocks/shared/pathDetails.js @@ -1,5 +1,6 @@ -import { CON_ORIGIN, DA_ORIGIN } from './constants.js'; +import { CON_ORIGIN } from './constants.js'; import { sanitizePathParts } from '../../scripts/utils.js'; +import { daApi } from './da-api.js'; let currpath; let currhash; @@ -16,16 +17,17 @@ function getOrgDetails({ editor, pathParts, ext }) { const parent = ext === null ? `/${fullPath}` : '/'; const parentName = ext === null ? pathParts[0] : 'Root'; const name = editor === 'config' && ext === null ? 'config' : pathParts[0]; - const daApi = editor === 'config' ? 'config' : 'source'; let path = ext === 'html' && !fullPath.endsWith('.html') ? `${fullPath}.html` : fullPath; if (editor === 'sheet' && !path.endsWith('.json')) path = `${path}.${ext}`; + const sourceUrl = editor === 'config' ? daApi.getConfigUrl(`/${path}`) : daApi.getSourceUrl(`/${path}`); + return { owner: pathParts[0], name, parent, parentName, - sourceUrl: `${DA_ORIGIN}/${daApi}/${path}`, + sourceUrl, }; } @@ -36,17 +38,18 @@ function getRepoDetails({ editor, pathParts, ext }) { const parent = ext === null ? `/${org}/${repo}` : `/${org}`; const parentName = ext === null ? repo : org; const name = editor === 'config' ? `${repo} config` : repo; - const daApi = editor === 'config' ? 'config' : 'source'; let path = ext === 'html' && !fullPath.endsWith('.html') ? `${fullPath}.html` : fullPath; if (editor === 'sheet' && !path.endsWith('.json')) path = `${path}.${ext}`; + const sourceUrl = editor === 'config' ? daApi.getConfigUrl(`/${path}`) : daApi.getSourceUrl(`/${path}`); + return { owner: org, repo, name, parent, parentName, - sourceUrl: `${DA_ORIGIN}/${daApi}/${path}`, + sourceUrl, previewUrl: `https://main--${repo}--${org}.aem.live`, contentUrl: `${CON_ORIGIN}/${fullPath}`, }; @@ -66,16 +69,17 @@ function getFullDetails({ editor, pathParts, ext }) { const parent = `/${pathParts.join('/')}`; const parentName = pathParts.pop(); - const daApi = editor === 'config' ? 'config' : 'source'; const path = ext === 'html' && !fullPath.endsWith('.html') && editor !== 'sheet' ? `${fullPath}.html` : fullPath; + const sourceUrl = editor === 'config' ? daApi.getConfigUrl(`/${path}`) : daApi.getSourceUrl(`/${path}`); + return { owner: org, repo, name: ext === null ? 'config' : name, parent: ext === null ? `${parent}/${name}` : parent, parentName: ext === null ? name : parentName, - sourceUrl: `${DA_ORIGIN}/${daApi}/${path}`, + sourceUrl, previewUrl: `https://main--${repo}--${org}.aem.live${pathname}`, contentUrl: `${CON_ORIGIN}/${fullPath}`, }; @@ -149,7 +153,7 @@ export default function getPathDetails(loc) { let path = ext === 'html' && !fullpath.endsWith('.html') ? `${fullpath}.html` : fullpath; if (editor === 'sheet' && !path.endsWith('.json')) path = `${path}.${ext}`; - details = { ...details, origin: DA_ORIGIN, fullpath: path, depth, view: editor }; + details = { ...details, origin: daApi.origin, fullpath: path, depth, view: editor }; return details; } diff --git a/blocks/shared/utils.js b/blocks/shared/utils.js index 53652cc2..fc31a030 100644 --- a/blocks/shared/utils.js +++ b/blocks/shared/utils.js @@ -1,80 +1,8 @@ -import { DA_ORIGIN, CON_ORIGIN, getLivePreviewUrl } from './constants.js'; +import { daFetch, initIms } from './fetch.js'; +import { daApi } from './da-api.js'; +import { CON_ORIGIN, getLivePreviewUrl } from './constants.js'; -const { getNx } = await import('../../scripts/utils.js'); - -// TODO: INFRA -const DA_ORIGINS = ['https://da.live', 'https://da.page', 'https://admin.da.live', 'https://admin.da.page', 'https://stage-admin.da.live', 'https://content.da.live', 'https://stage-content.da.live', 'http://localhost:8787']; -const AEM_ORIGINS = ['https://admin.hlx.page', 'https://admin.aem.live']; -const ALLOWED_TOKEN = [...DA_ORIGINS, ...AEM_ORIGINS]; - -let imsDetails; - -export async function initIms() { - if (imsDetails) return imsDetails; - const { loadIms } = await import(`${getNx()}/utils/ims.js`); - - try { - imsDetails = await loadIms(); - return imsDetails; - } catch { - return null; - } -} - -export const daFetch = async (url, opts = {}) => { - opts.headers = opts.headers || {}; - let accessToken; - if (localStorage.getItem('nx-ims')) { - ({ accessToken } = await initIms()); - const canToken = ALLOWED_TOKEN.some((origin) => new URL(url).origin === origin); - if (accessToken && canToken) { - opts.headers.Authorization = `Bearer ${accessToken.token}`; - if (AEM_ORIGINS.some((origin) => new URL(url).origin === origin)) { - opts.headers['x-content-source-authorization'] = `Bearer ${accessToken.token}`; - } - } - } - const resp = await fetch(url, opts); - if (resp.status === 401 && opts.noRedirect !== true) { - // Only attempt sign-in if the request is for DA. - if (DA_ORIGINS.some((origin) => url.startsWith(origin))) { - // If the user has an access token, but are not permitted, redirect them to not found. - if (accessToken) { - // eslint-disable-next-line no-console - console.warn('You see the 404 page because you have no access to this page', url); - window.location = `${window.location.origin}/not-found`; - return { ok: false }; - } - // eslint-disable-next-line no-console - console.warn('You need to sign in because you are not authorized to access this page', url); - const { loadIms, handleSignIn } = await import(`${getNx()}/utils/ims.js`); - await loadIms(); - handleSignIn(); - } - } - - // TODO: Properly support 403 - DA Admin sometimes gives 401s and sometimes 403s. - if (resp.status === 403) { - return resp; - } - - // If child actions header is present, use it. - // This is a hint as to what can be done with the children. - if (resp.headers?.get('x-da-child-actions')) { - resp.permissions = resp.headers.get('x-da-child-actions').split('=').pop().split(','); - return resp; - } - - // Use the self actions hint if child actions are not present. - if (resp.headers?.get('x-da-actions')) { - resp.permissions = resp.headers?.get('x-da-actions')?.split('=').pop().split(','); - return resp; - } - - // Support legacy admin.role.all - resp.permissions = ['read', 'write']; - return resp; -}; +export { daFetch, initIms }; export async function aemAdmin(path, api, method = 'POST') { const [owner, repo, ...parts] = path.slice(1).split('/'); @@ -91,18 +19,11 @@ export async function aemAdmin(path, api, method = 'POST') { } } -export async function saveToDa({ path, formData, blob, props, preview = false }) { - const opts = { method: 'PUT' }; - - const form = formData || new FormData(); - if (blob || props) { - if (blob) form.append('data', blob); - if (props) form.append('props', JSON.stringify(props)); - } - if ([...form.keys()].length) opts.body = form; - - const daResp = await daFetch(`${DA_ORIGIN}/source${path}`, opts); - if (!daResp.ok) return undefined; +export async function saveToDa({ + path, formData, blob, props, preview = false, method = 'PUT', +}) { + const daResp = await daApi.saveSource(path, { formData, blob, props, method }); + if (!daResp || !daResp.ok) return undefined; if (!preview) return undefined; return aemAdmin(path, 'preview'); } @@ -141,7 +62,7 @@ export async function livePreviewLogin(owner, repo) { */ export async function checkLockdownImages(owner) { try { - const resp = await daFetch(`${DA_ORIGIN}/config/${owner}`); + const resp = await daApi.getConfig(`/${owner}`); if (!resp.ok) return false; const config = await resp.json(); diff --git a/blocks/start/index.js b/blocks/start/index.js index dc8edf78..91314453 100644 --- a/blocks/start/index.js +++ b/blocks/start/index.js @@ -1,6 +1,6 @@ import { getNx, sanitizePathParts } from '../../scripts/utils.js'; -import { DA_ORIGIN } from '../shared/constants.js'; import { daFetch } from '../shared/utils.js'; +import { daApi } from '../shared/da-api.js'; const { crawl } = await import(`${getNx()}/public/utils/tree.js`); @@ -47,13 +47,13 @@ async function bulkAemAdmin(org, site, files) { } export async function copyConfig(sourcePath, org, site) { - const destText = await getText(sourcePath, org, site, `${DA_ORIGIN}/config${sourcePath}/`); + const destText = await getText(sourcePath, org, site, daApi.getConfigUrl(`${sourcePath}/`)); if (!destText) return { ok: false }; const body = new FormData(); body.append('config', destText); const opts = { method: 'PUT', body }; - return daFetch(`${DA_ORIGIN}/config/${org}/${site}/`, opts); + return daFetch(daApi.getConfigUrl(`/${org}/${site}/`), opts); } export async function copyContent(sourcePath, org, site, setStatus) { @@ -69,13 +69,13 @@ export async function copyContent(sourcePath, org, site, setStatus) { let blob; if (ext === 'json' || ext === 'html' || ext === 'svg') { - const destText = await getText(sourcePath, org, site, `${DA_ORIGIN}/source${path}`); + const destText = await getText(sourcePath, org, site, daApi.getSourceUrl(path)); if (destText) { const type = MIME_TYPES[ext]; blob = new Blob([destText], { type }); } } else { - const sourceBlob = await getBlob(`${DA_ORIGIN}/source${path}`); + const sourceBlob = await getBlob(daApi.getSourceUrl(path)); if (sourceBlob) blob = sourceBlob; } @@ -87,11 +87,7 @@ export async function copyContent(sourcePath, org, site, setStatus) { // Save the file const savePath = path.replace(sourcePath, `/${org}/${site}`); - const body = new FormData(); - - body.append('data', blob); - const opts = { method: 'POST', body }; - const putResp = await daFetch(`${DA_ORIGIN}/source${savePath}`, opts); + const putResp = await daApi.saveSource(savePath, { blob, method: 'POST' }); file.ok = putResp.ok; }; diff --git a/blocks/start/start.js b/blocks/start/start.js index 262785dc..62abc005 100644 --- a/blocks/start/start.js +++ b/blocks/start/start.js @@ -1,14 +1,12 @@ import { LitElement, html, nothing } from 'da-lit'; -import { getDaAdmin } from '../shared/constants.js'; import getSheet from '../shared/sheet.js'; import { daFetch } from '../shared/utils.js'; +import { daApi } from '../shared/da-api.js'; import { copyConfig, copyContent, previewContent } from './index.js'; import { sanitizePathParts } from '../../scripts/utils.js'; const sheet = await getSheet('/blocks/start/start.css'); -const DA_ORIGIN = getDaAdmin(); - const AEM_TEMPLATES = [ { title: 'AEM Boilerplate', @@ -71,7 +69,7 @@ async function fetchConfig(org, body) { let opts; if (body) opts = { method: 'POST', body }; - return daFetch(`${DA_ORIGIN}/config/${org}/`, opts); + return daApi.getConfig(`/${org}/`, opts); } export async function loadConfig(org) { @@ -170,7 +168,7 @@ class DaStart extends LitElement { if (hasDemo) { this._disableCreate = true; - const resp = await daFetch(`${DA_ORIGIN}/list/${this.org}/${this.site}`); + const resp = await daApi.getList(`/${this.org}/${this.site}`); const json = await resp.json(); if (json.length > 0) { this._errorText = 'The target site is not empty. Choose no demo content or a different site.'; @@ -291,7 +289,7 @@ class DaStart extends LitElement { renderOne() { return html`
-
+