Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
20 changes: 16 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
<!--
Copyright 2025 Digital Bazaar, Inc.
Copyright 2025-2026 Digital Bazaar, Inc.

SPDX-License-Identifier: BSD-3-Clause
-->

<html>
<head>
<title>Credential Viewer</title>
<meta http-equiv="content-security-policy" content="frame-src 'none'">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.0/semantic.min.css" integrity="sha512-PwhgdrueUt7iVICnZMjYcbiLalCztrVfzUIYXekIK8hZu4DQP141GrKh6fUHmNERWi4bGdBXIZqtBZnsSzHEMg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/4.1.0/mustache.min.js" integrity="sha512-HYiNpwSxYuji84SQbCU5m9kHEsRqwWypXgJMBtbRSumlx1iBB6QaxgEBZHSHEGM+fKyCX/3Kb5V5jeVXm0OglQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script type="importmap">
{
"imports": {
"@digitalbazaar/vc-html-renderer": "./vendor/vc-html-renderer/index.js"
}
}
</script>
<script type="module">
import './app.js';
</script>
Expand Down Expand Up @@ -57,7 +63,10 @@
@click="currentTab = 'credential'">Filtered Credential</div>
</div>
<div class="ui bottom attached tab segment" :class="{ active: currentTab == 'rendered' }">
<iframe sandbox="allow-scripts allow-modals" :srcdoc="shimCode()" style="width: 100%; border: none;"></iframe>
<!-- before -->
<!-- <iframe sandbox="allow-scripts allow-modals" :srcdoc="shimCode()" style="width: 100%; border: none;"></iframe> -->
<!-- after -->
<div class="vc-html-render-mount" style="width: 100%;" @vue:mounted="renderInto($el)"></div>
</div>
<div class="ui bottom attached tab segment" :class="{ active: currentTab == 'code' }">
<div class="ui form">
Expand Down Expand Up @@ -108,7 +117,10 @@
<div v-scope="SVGViewer({idx, credential: store.credential})" @vue:mounted="mounted"></div>
</div>
<div class="column" v-else-if="renderMethod.type === 'TemplateRenderMethod' && renderMethod.renderSuite === 'html'">
<div v-scope="HTMLViewer({template: renderMethod?.template, credential: store.credential, pointers: renderMethod.renderProperty})"></div>
<!-- before -->
<!-- <div v-scope="HTMLViewer({template: renderMethod?.template, credential: store.credential, pointers: renderMethod.renderProperty})"></div> -->
<!-- after -->
<div v-scope="HTMLViewer({renderMethod, credential: store.credential})"></div>
</div>
<div class="column" v-else>
<div v-text="'No viewer available for render method type: ' + renderMethod.type"></div>
Expand Down
49 changes: 49 additions & 0 deletions vendor/vc-html-renderer/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*!
* Copyright (c) 2026 Digital Bazaar, Inc.
*/

// HTML Render Suite constants. Tags: [spec] = mandated by
// https://w3c.github.io/vc-render-method/#the-html-render-suite ;
// [lib] = library-defined (not spec-mandated).

// [spec] HTML suite = `TemplateRenderMethod` with `renderSuite: "html"`.
export const RENDER_METHOD_TYPE = 'TemplateRenderMethod';
export const HTML_RENDER_SUITE = 'html';

// [spec] wrapper/template-frame CSP: inline + `data:` only, no network.
export const TEMPLATE_CSP = `default-src data: 'unsafe-inline'`;

// [spec] host-frame CSP requires `frame-src 'none'` (allows `srcdoc`, blocks
// `<iframe src>` -> isolates the template and preserves the app's CHAPI
// framing). The remaining directives are added hardening (not spec-mandated):
// `default-src 'none'` blocks all network; `script-src`/`style-src
// 'unsafe-inline'` admit only the host controller's inline script and styles.
export const HOST_CSP = `default-src 'none'; script-src 'unsafe-inline'; ` +
`style-src 'unsafe-inline'; frame-src 'none'`;

// [spec] template-frame sandbox; no `allow-same-origin` -> opaque origin.
export const TEMPLATE_SANDBOX = 'allow-scripts';

// [lib] reject `handle.ready` if no ready/error by this time; `0` disables.
export const DEFAULT_TIMEOUT = 10000;

// Transports: app<->host via `postMessage` [lib]; host<->template via a
// transferred `MessageChannel` port [spec].
export const MESSAGE_NAMESPACE = '@digitalbazaar/vc-html-renderer';

export const MESSAGE_TYPE = {
// [lib] host->app: controller ready to receive the template
HOST_READY: `${MESSAGE_NAMESPACE}:host-ready`,
// [lib] app->host: deliver template document + options
RENDER: `${MESSAGE_NAMESPACE}:render`,
// [spec] host->template: transfer the MessageChannel port
PORT: `${MESSAGE_NAMESPACE}:port`,
// [lib] host->app: template frame `load` fired (drives no-signal fallback)
LOADED: `${MESSAGE_NAMESPACE}:loaded`,
// [spec] renderMethodReady() success (template->host port->app)
READY: `${MESSAGE_NAMESPACE}:ready`,
// [spec] renderMethodReady(Error) (template->host port->app)
ERROR: `${MESSAGE_NAMESPACE}:error`,
// [lib] content size changed (template->host port->app)
RESIZE: `${MESSAGE_NAMESPACE}:resize`
};
85 changes: 85 additions & 0 deletions vendor/vc-html-renderer/detect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*!
* Copyright (c) 2026 Digital Bazaar, Inc.
*/
import {HTML_RENDER_SUITE, RENDER_METHOD_TYPE} from './constants.js';
import {selectJsonLd} from './select.js';

/**
* Check if a verifiable credential has an HTML Render Method.
*
* @param {object} options - Options.
* @param {object} options.credential - The verifiable credential.
*
* @returns {boolean} `true` if an HTML render method is present.
*/
export function supportsHtml({credential} = {}) {
return findHtmlRenderMethods({credential}).length > 0;
}

/**
* Find all HTML Render Methods on a verifiable credential.
*
* [spec] An HTML Render Method is a `TemplateRenderMethod` whose `renderSuite`
* is `"html"`. `renderMethod` may be a single object or an array.
*
* @param {object} options - Options.
* @param {object} options.credential - The verifiable credential.
*
* @returns {Array} The matching render method objects (possibly empty).
*/
export function findHtmlRenderMethods({credential} = {}) {
let renderMethods = credential?.renderMethod;
if(!renderMethods) {
return [];
}
// normalize a single render method to an array
if(!Array.isArray(renderMethods)) {
renderMethods = [renderMethods];
}
return renderMethods.filter(_isHtmlRenderMethod);
}

/**
* Find the first HTML Render Method on a verifiable credential.
*
* @param {object} options - Options.
* @param {object} options.credential - The verifiable credential.
*
* @returns {object|null} The first matching render method, or `null`.
*/
export function findHtmlRenderMethod({credential} = {}) {
const [renderMethod = null] = findHtmlRenderMethods({credential});
return renderMethod;
}

/**
* Filter a credential to the fields named by a render method's
* `renderProperty` JSON pointers (selective disclosure).
*
* [spec] MUST filter with the `selectJsonLd` algorithm from VC-DI-ECDSA over
* the RFC-6901 `renderProperty` pointers; an absent `renderProperty` exposes
* the whole credential. Every pointer must resolve or `selectJsonLd` throws.
*
* @param {object} options - Options.
* @param {object} options.credential - The verifiable credential.
* @param {object} options.renderMethod - The HTML render method.
*
* @returns {object} The filtered credential.
*/
export function filterCredential({credential, renderMethod} = {}) {
// [lib] deep-clone to strip framework reactive proxies (e.g. Vue); the
// selection algorithm requires a plain object
const document = JSON.parse(JSON.stringify(credential));
const pointers = renderMethod?.renderProperty;
if(!Array.isArray(pointers) || pointers.length === 0) {
// [spec] no `renderProperty`: expose the whole credential
return document;
}
return selectJsonLd({document, pointers});
}

// [spec] discriminator: `TemplateRenderMethod` + `renderSuite: "html"`
function _isHtmlRenderMethod(renderMethod) {
return renderMethod?.type === RENDER_METHOD_TYPE &&
renderMethod?.renderSuite === HTML_RENDER_SUITE;
}
Loading