Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,073 changes: 1,894 additions & 179 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"@adobe/helix-admin-support": "5.0.3",
"@adobe/helix-config": "5.7.0",
"@adobe/helix-google-support": "4.0.1",
"@adobe/helix-html2md": "1.1.0",
"@adobe/helix-mediahandler": "2.9.5",
"@adobe/helix-onedrive-support": "12.1.5",
"@adobe/helix-shared-body-data": "2.2.3",
Expand Down
4 changes: 4 additions & 0 deletions src/contentproxy/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ const errors = [
code: 'AEM_BACKEND_FILE_TOO_BIG',
template: 'Unable to preview \'$1\': Documents larger than 100mb not supported: $2',
},
{
code: 'AEM_BACKEND_TOO_MANY_IMAGES',
template: 'Unable to preview \'$1\': Documents has more than $2 images: $3',
},
{
code: 'AEM_BACKEND_RESOURCE_TOO_BIG',
template: 'Files larger than 500mb are not supported: $1',
Expand Down
8 changes: 7 additions & 1 deletion src/contentproxy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { error } from './errors.js';
import google from './google.js';
import markup from './markup.js';
import onedrive from './onedrive.js';
import sourcebus from './sourcebus.js';

/**
* @type {import('./contentproxy').ContentSourceHandler[]}
Expand All @@ -25,6 +26,7 @@ export const HANDLERS = { // exported for testing only
google,
onedrive,
markup,
sourcebus,
};

/**
Expand All @@ -34,7 +36,11 @@ export const HANDLERS = { // exported for testing only
* @return {import('./contentproxy').ContentSourceHandler} handler
*/
export function getContentSourceHandler(source) {
return HANDLERS[source.type];
let { type } = source;
if (source.url?.startsWith('https://api.aem.live/')) {
type = 'sourcebus';
}
return HANDLERS[type];
}

/**
Expand Down
157 changes: 157 additions & 0 deletions src/contentproxy/sourcebus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* Copyright 2025 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import { HelixStorage } from '@adobe/helix-shared-storage';
import { MediaHandler, SizeTooLargeException } from '@adobe/helix-mediahandler';
import { ConstraintsError, html2md, TooManyImagesError } from '@adobe/helix-html2md';
import { Response } from '@adobe/fetch';
import { errorResponse } from '../support/utils.js';
import { error } from './errors.js';

const DEFAULT_MAX_IMAGE_SIZE = 20 * 1024 * 1024; // 20mb

const DEFAULT_MAX_IMAGES = 200;

/**
* Retrieves a file from source bus.
*
* @param {import('../support/AdminContext').AdminContext} ctx context
* @param {import('../support/RequestInfo').RequestInfo} info request info
* @param {object} [opts] options
* @param {object} [opts.source] content source
* @param {string} [opts.lastModified] last modified
* @param {number} [opts.fetchTimeout] fetch timeout
* @returns {Promise<Response>} response
*/
async function handle(ctx, info, opts) {
const { config: { content, limits }, log } = ctx;

const source = opts?.source ?? content.source;
const sourceUrl = new URL(source.url);
// extract org and site from url.pathname, format: https://api.aem.live/<org>/sites/<site>/source
// e.g. /adobe/sites/foo/source
const pathMatch = sourceUrl.pathname.match(/^\/([^/]+)\/sites\/([^/]+)\/source$/);
if (!pathMatch) {
return errorResponse(log, 400, error(
'Source url must be in the format: https://api.aem.live/<org>/sites/<site>/source. Got: $1',
sourceUrl.href,
));
}
const [, org, site] = pathMatch; // eslint-disable-line prefer-destructuring

// for now, only allow source bus from the same org and site
if (org !== info.org || site !== info.site) {
return errorResponse(log, 400, error(
'Source bus is not allowed for org: $1, site: $2',
org,
site,
));
}
// the source is stored as .html files in the source bus
let sourcePath = info.resourcePath;
if (info.ext === '.md') {
sourcePath = `${sourcePath.substring(0, sourcePath.length - '.md'.length)}.html`;
/* c8 ignore next 7 */
} else {
// this should never happen, since all resourcePaths are properly mapped before
return errorResponse(log, 400, error(
'unexpected file extension: $1',
info.ext,
));
}

// load content from source bus
const sourceBus = HelixStorage.fromContext(ctx).sourceBus();
const meta = {};
const body = await sourceBus.get(`${org}/${site}${sourcePath}`, meta);
if (!body) {
return new Response('', { status: 404 });
}

const {
MEDIAHANDLER_NOCACHHE: noCache,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIAHANDLER_NOCACHHE is probably a typo. (Two 'H's)

CLOUDFLARE_ACCOUNT_ID: r2AccountId,
CLOUDFLARE_R2_ACCESS_KEY_ID: r2AccessKeyId,
CLOUDFLARE_R2_SECRET_ACCESS_KEY: r2SecretAccessKey,
} = ctx.env;

const mediaHandler = new MediaHandler({
r2AccountId,
r2AccessKeyId,
r2SecretAccessKey,
bucketId: ctx.attributes.bucketMap.media,
owner: org,
repo: site,
ref: 'main',
contentBusId: content.contentBusId,
log,
noCache,
fetchTimeout: 5000, // limit image fetches to 5s
forceHttp1: true,
maxSize: limits?.html2md?.maxImageSize ?? DEFAULT_MAX_IMAGE_SIZE,
});

const maxImages = limits?.html2md?.maxImages ?? DEFAULT_MAX_IMAGES;
try {
// convert to md
const md = await html2md(body, {
mediaHandler,
log,
url: sourceUrl.href + sourcePath, // only used for logging
org,
site,
unspreadLists: true,
maxImages,
externalImageUrlPrefixes: [`https://main--${site}--${org}.aem.page/`],
});

return new Response(md, {
status: 200,
headers: {
'content-type': 'text/markdown',
'last-modified': meta.LastModified?.toUTCString(),
},
});
} catch (e) {
if (e instanceof TooManyImagesError) {
return errorResponse(log, 409, error(
'Unable to preview \'$1\': Documents has more than $2 images: $3',
sourcePath,
maxImages,
e.message, // todo: include num images in error
));
}
if (e instanceof SizeTooLargeException) {
return errorResponse(log, 409, error(
'Unable to preview \'$1\': $2',
sourcePath,
e.message,
));
}
/* c8 ignore next 6 */
return errorResponse(log, 500, error(
'Unable to preview \'$1\': $2',
sourcePath,
e.message,
));
}
}

/**
* @type {import('./contentproxy.js').ContentSourceHandler}
*/
export default {
name: 'sourcebus',
handle,
handleJSON: () => { throw new Error('not implemented'); },
handleFile: () => { throw new Error('not implemented'); },
list: () => { throw new Error('not implemented'); },
};
Binary file added test/contentproxy/fixtures/sourcebus/300.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions test/contentproxy/fixtures/sourcebus/gallery.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html>
<head></head>
<body>
<main>
<div>
<h1>Hello, world.</h1>
<p>source bus images.</p>
<img src="https://www.example.com/image1.jpg">
<img src="https://www.example.com/image2.jpg">
<img src="https://main--site--org.aem.page/media_2c2e2c6c049ccf4b583431e14919687f3a39cc227.png#width=300&height=300">
</div>
</main>
</body>
</html>
11 changes: 11 additions & 0 deletions test/contentproxy/fixtures/sourcebus/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<html>
<head></head>
<body>
<main>
<div>
<h1>Hello, world.</h1>
<p>Testing, source bus.</p>
</div>
</main>
</body>
</html>
11 changes: 11 additions & 0 deletions test/contentproxy/fixtures/sourcebus/welcome.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<html>
<head></head>
<body>
<main>
<div>
<h1>Hello, world.</h1>
<p>Testing, source bus.</p>
</div>
</main>
</body>
</html>
Loading