SOURCE / PINNED RELEASE
Made of little things.
Envelope opener
- Release
- eabb9c3e84df…
- Author-recorded commit
- Not provided
- License
- Check the original source
The hash-verified playable HTML, which may contain bundled or minified code. This view does not execute it.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Envelope opener</title>
<style>
:root { color-scheme: dark; font: 16px/1.5 system-ui, sans-serif; background: #111; color: #eee; }
body { min-height: 100vh; margin: 0; display: grid; place-items: center; padding: 1.5rem; }
main { width: min(32rem, 100%); display: grid; gap: .9rem; }
h1 { margin: 0; font-size: 1rem; letter-spacing: .08em; text-transform: uppercase; }
p { margin: 0; color: #aaa; }
form { display: grid; gap: .65rem; }
input, button { padding: .65rem .75rem; border-radius: .3rem; font: inherit; }
input { background: #1c1c1c; color: #eee; border: 1px solid #555; }
button { border: 0; background: #b9e6b9; color: #111; cursor: pointer; }
[hidden] { display: none; }
[data-state="error"] { color: #ffaaa5; }
</style>
</head>
<body>
<main>
<h1>Envelope opener</h1>
<p id="status" role="status" aria-live="polite">Waiting for launch fragment…</p>
<form id="unlock" hidden>
<label for="password">Password</label>
<input id="password" name="password" type="password" autocomplete="current-password" minlength="8" required>
<button type="submit">Open encrypted link</button>
</form>
</main>
<script>
(() => {
const codec = (function createFragmentDecoder(runtime = globalThis) {
const publicPrefix = 'nw1.';
const encryptedPrefix = 'nwe1.';
const maxFragmentLength = 8_192;
const maxStateBytes = 4_096;
const maxPointerLength = 512;
const iterations = 120_000;
const pointerPattern = /^(?:naddr|nevent)1[023456789acdefghjklmnpqrstuvwxyz]+$/;
function decodeFragmentValue(hash = '') {
if (typeof hash !== 'string') throw new Error('fragment must be a string');
const raw = hash.startsWith('#') ? hash.slice(1) : hash;
if (raw === '') return null;
if (raw.startsWith(encryptedPrefix)) throw new Error('encrypted fragment requires a password');
if (raw.length > maxFragmentLength) throw new Error(`invalid fragment: maximum is ${maxFragmentLength} characters`);
if (!raw.startsWith(publicPrefix)) throw new Error('unsupported fragment version');
try {
const decoded = JSON.parse(decodeBase64Url(raw.slice(publicPrefix.length)));
validatePayload(decoded);
return {
version: 1,
pointer: decoded.pointer,
...(Object.hasOwn(decoded, 'state') ? { state: decoded.state } : { state: undefined }),
...(Object.hasOwn(decoded, 'intent') ? { intent: normalizeIntentValue(decoded.intent) } : {}),
};
} catch (error) {
if (error instanceof Error && /pointer|state|version|payload|intent/.test(error.message)) throw error;
throw new Error('invalid fragment payload');
}
}
function isEncryptedFragmentValue(hash = '') {
return typeof hash === 'string'
&& (hash.startsWith(`#${encryptedPrefix}`) || hash.startsWith(encryptedPrefix));
}
async function decodeEncryptedFragmentValue(hash, password) {
if (typeof hash !== 'string') throw new Error('fragment must be a string');
const raw = hash.startsWith('#') ? hash.slice(1) : hash;
if (!raw.startsWith(encryptedPrefix)) throw new Error('not an encrypted fragment');
if (raw.length > maxFragmentLength) throw new Error('invalid encrypted fragment');
validatePassword(password);
const parts = raw.slice(encryptedPrefix.length).split('.');
if (parts.length !== 3) throw new Error('invalid encrypted fragment or password');
try {
const salt = decodeBase64UrlBytes(parts[0]);
const iv = decodeBase64UrlBytes(parts[1]);
const ciphertext = decodeBase64UrlBytes(parts[2]);
if (salt.byteLength !== 16 || iv.byteLength !== 12 || ciphertext.byteLength < 17) {
throw new Error('invalid encrypted fragment');
}
const key = await derivePasswordKey(password, salt);
const plaintext = await getCrypto().subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext);
const decoded = JSON.parse(new runtime.TextDecoder().decode(plaintext));
validatePayload(decoded);
return {
version: 1,
pointer: decoded.pointer,
...(Object.hasOwn(decoded, 'state') ? { state: decoded.state } : { state: undefined }),
...(Object.hasOwn(decoded, 'intent') ? { intent: normalizeIntentValue(decoded.intent) } : {}),
};
} catch {
throw new Error('invalid encrypted fragment or password');
}
}
function validatePayload(value) {
if (!isPlainObjectValue(value) || value.v !== 1) throw new Error('invalid fragment payload version');
validatePointer(value.pointer);
if (Object.hasOwn(value, 'state')) {
const stateJson = stringifyJsonValue(value.state);
if (new runtime.TextEncoder().encode(stateJson).byteLength > maxStateBytes) {
throw new Error(`launch state is too large; maximum is ${maxStateBytes} bytes`);
}
}
if (Object.hasOwn(value, 'intent')) normalizeIntentValue(value.intent);
}
function normalizeIntentValue(value) {
if (!isPlainObjectValue(value)) throw new Error('launch intent must be an object');
const archetype = validateIntentSlug(value.archetype, 'archetype');
const convention = value.convention;
if (
typeof convention !== 'string'
|| convention.length > 256
|| !/^napplet:[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/.test(convention)
) throw new Error('launch intent convention must be a bounded napplet convention');
const intent = { archetype, convention };
if (Object.hasOwn(value, 'action')) intent.action = validateIntentSlug(value.action, 'action');
if (Object.hasOwn(value, 'payload')) intent.payload = cloneJsonValue(value.payload, 'intent payload');
if (Object.hasOwn(value, 'behavior')) intent.behavior = normalizeIntentBehavior(value.behavior);
const serialized = stringifyJsonValue(intent);
if (new runtime.TextEncoder().encode(serialized).byteLength > maxStateBytes) {
throw new Error(`launch intent is too large; maximum is ${maxStateBytes} bytes`);
}
return intent;
}
function validateIntentSlug(value, field) {
if (typeof value !== 'string' || value.length > 64 || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value)) {
throw new Error(`launch intent ${field} must be a lowercase slug`);
}
return value;
}
function normalizeIntentBehavior(value) {
if (!isPlainObjectValue(value)) throw new Error('launch intent behavior must be an object');
const behavior = {};
for (const field of ['focus', 'newWindow', 'reuse']) {
if (!Object.hasOwn(value, field)) continue;
if (typeof value[field] !== 'boolean') throw new Error(`launch intent behavior ${field} must be boolean`);
behavior[field] = value[field];
}
if (Object.keys(value).some((field) => !['focus', 'newWindow', 'reuse'].includes(field))) {
throw new Error('launch intent behavior contains an unsupported field');
}
return behavior;
}
function cloneJsonValue(value, label) {
const serialized = stringifyJsonValue(value);
if (new runtime.TextEncoder().encode(serialized).byteLength > maxStateBytes) {
throw new Error(`${label} is too large; maximum is ${maxStateBytes} bytes`);
}
return JSON.parse(serialized);
}
function validatePointer(pointer) {
if (typeof pointer !== 'string' || pointer.length === 0 || pointer.length > maxPointerLength) {
throw new Error('pointer must be a bounded naddr or nevent string');
}
if (!pointerPattern.test(pointer)) throw new Error('pointer must be an naddr or nevent NIP-19 string');
}
function validatePassword(password) {
if (typeof password !== 'string' || password.length < 8) throw new Error('password must be at least 8 characters');
}
function getCrypto() {
if (!runtime.crypto?.subtle) throw new Error('Web Crypto API is unavailable');
return runtime.crypto;
}
async function derivePasswordKey(password, salt) {
const webCrypto = getCrypto();
const material = await webCrypto.subtle.importKey(
'raw',
new runtime.TextEncoder().encode(password),
'PBKDF2',
false,
['deriveKey'],
);
return webCrypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations, hash: 'SHA-256' },
material,
{ name: 'AES-GCM', length: 256 },
false,
['decrypt'],
);
}
function decodeBase64Url(value) {
const bytes = decodeBase64UrlBytes(value);
return new runtime.TextDecoder().decode(bytes);
}
function decodeBase64UrlBytes(value) {
if (!/^[A-Za-z0-9_-]+$/.test(value) || value.length % 4 === 1) throw new Error('invalid base64url');
const padded = value.replaceAll('-', '+').replaceAll('_', '/') + '='.repeat((4 - (value.length % 4)) % 4);
const binary = runtime.atob(padded);
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
}
function isPlainObjectValue(value) {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
function stringifyJsonValue(value) {
let result;
try {
result = JSON.stringify(value);
} catch {
throw new Error('launch state must be JSON-compatible');
}
if (result === undefined) throw new Error('launch state must be JSON-compatible');
return result;
}
return Object.freeze({
decodeFragment: decodeFragmentValue,
isEncryptedFragment: isEncryptedFragmentValue,
decodeEncryptedFragment: decodeEncryptedFragmentValue,
});
})(globalThis);
const controller = (function createOpenerNappletController(runtime, codec) {
const parentWindow = runtime.parent;
const status = runtime.document.querySelector('#status');
const unlock = runtime.document.querySelector('#unlock');
const password = runtime.document.querySelector('#password');
let shellReady = false;
let fragment;
let decoding = false;
let started = false;
let requestSequence = 0;
let pendingLaunch = null;
const setStatus = (message, state) => {
status.textContent = message;
if (state) status.dataset.state = state;
else delete status.dataset.state;
};
const reportError = (error, encrypted = false) => {
const message = encrypted
? 'This encrypted link could not open. Check the password and try again.'
: 'This link could not open safely.';
setStatus(message, 'error');
parentWindow.postMessage({
type: 'opener.error',
version: 1,
error: error instanceof Error ? error.message : String(error),
}, '*');
};
const launch = (request) => {
pendingLaunch = null;
if (request === null) {
setStatus('Choose a napplet to open.', 'empty');
return;
}
const requestId = `launch-${++requestSequence}`;
pendingLaunch = { requestId, request };
parentWindow.postMessage({
type: 'opener.open',
version: 1,
requestId,
pointer: request.pointer,
}, '*');
setStatus('Verifying napplet…', 'loading');
};
const decodePublic = () => {
try {
launch(codec.decodeFragment(fragment));
} catch (error) {
reportError(error);
}
};
const consume = () => {
if (!shellReady || fragment === undefined || decoding) return;
pendingLaunch = null;
unlock.hidden = true;
if (codec.isEncryptedFragment(fragment)) {
setStatus('Enter the password to decrypt this link.', 'locked');
unlock.hidden = false;
password.focus();
return;
}
decodePublic();
};
const normalizeContracts = (contracts) => {
if (!Array.isArray(contracts)) return [];
return contracts.filter((contract) => (
contract !== null
&& typeof contract === 'object'
&& typeof contract.archetype === 'string'
&& contract.archetype.length > 0
&& typeof contract.convention === 'string'
&& contract.convention.length > 0
));
};
const createIntentRequest = (request, contracts) => {
const advertised = normalizeContracts(contracts);
const requested = request.intent;
const contract = requested
? advertised.find((candidate) => (
candidate.archetype === requested.archetype
&& candidate.convention === requested.convention
))
: advertised[0];
if (!contract) throw new Error('the target does not advertise the requested intent contract');
const intent = {
archetype: contract.archetype,
action: requested?.action ?? 'open',
convention: contract.convention,
};
if (requested && Object.hasOwn(requested, 'payload')) intent.payload = requested.payload;
else if (request.state !== undefined) intent.payload = request.state;
if (requested?.behavior !== undefined) intent.behavior = requested.behavior;
return intent;
};
const invokeTarget = async (message) => {
const pending = pendingLaunch;
if (
!pending
|| message.requestId !== pending.requestId
|| message.pointer !== pending.request.pointer
) return;
pendingLaunch = null;
try {
const intentApi = runtime.napplet?.intent;
if (typeof intentApi?.invoke !== 'function') throw new Error('the Paja runtime did not grant NAP-INTENT');
const result = await intentApi.invoke(createIntentRequest(pending.request, message.contracts));
if (!result || result.ok !== true || result.handled !== true) {
throw new Error(result?.error ?? 'the target did not handle the launch intent');
}
setStatus('Napplet opened.', 'ready');
} catch (error) {
reportError(error);
}
};
const onSubmit = async (event) => {
event.preventDefault();
if (decoding || !codec.isEncryptedFragment(fragment)) return;
decoding = true;
setStatus('Decrypting link…', 'loading');
try {
launch(await codec.decodeEncryptedFragment(fragment, password.value));
unlock.hidden = true;
} catch (error) {
reportError(error, true);
unlock.hidden = false;
password.select();
} finally {
decoding = false;
}
};
const onMessage = (event) => {
if (event.source !== parentWindow) return;
const data = event.data;
if (!data || typeof data.type !== 'string') return;
if (data.type === 'shell.init') {
shellReady = true;
consume();
return;
}
if (data.type === 'opener.fragment' && typeof data.fragment === 'string') {
fragment = data.fragment;
consume();
return;
}
if (data.type === 'opener.target.ready' && data.version === 1) {
void invokeTarget(data);
return;
}
if (
data.type === 'opener.target.error'
&& pendingLaunch
&& data.requestId === pendingLaunch.requestId
) {
pendingLaunch = null;
reportError(new Error(typeof data.message === 'string' ? data.message : 'target failed to open'));
}
};
function start() {
if (started) return;
started = true;
unlock.addEventListener('submit', onSubmit);
runtime.addEventListener('message', onMessage);
}
return Object.freeze({ start });
})(globalThis, codec);
controller.start();
})();
</script>
</body>
</html>