pg-sveltekit
GitHub · TypeScript · SvelteKit Example
A SvelteKit application demonstrating PostGuard file encryption and decryption in a web browser using @e4a/pg-js. Part of the postguard-examples repository.
Running
cd pg-sveltekit
npm install
npm run devSetup
You need two Vite plugins for WASM support:
npm install -D vite-plugin-wasm vite-plugin-top-level-awaitimport { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import wasm from 'vite-plugin-wasm';
import topLevelAwait from 'vite-plugin-top-level-await';
export default defineConfig({
plugins: [sveltekit(), wasm(), topLevelAwait()]
});No Node.js polyfills are needed. The SDK handles browser compatibility internally.
Configure the PKG and Cryptify URLs via environment variables. Keep the API key server-side only:
# Public (available in browser)
PUBLIC_PKG_URL=https://pkg.staging.yivi.app
PUBLIC_CRYPTIFY_URL=https://fileshare.staging.yivi.app
PUBLIC_APP_NAME=PostGuard for Business Example
# Server-only
PG_API_KEY=PG-API-your-key-hereimport { env } from '$env/dynamic/private';
export const PG_API_KEY = env['PG_API_KEY'] ?? '';import { env } from '$env/dynamic/public';
export const APP_NAME = env.PUBLIC_APP_NAME || 'PostGuard for Business Example';
export const PKG_URL = env.PUBLIC_PKG_URL || 'https://pkg.staging.yivi.app';
export const CRYPTIFY_URL = env.PUBLIC_CRYPTIFY_URL || 'https://fileshare.staging.yivi.app';Encrypt and Upload Files
Create a module that initializes PostGuard and wraps the encryption call:
import { PostGuard } from '@e4a/pg-js';
import type { CitizenRecipient, OrganisationRecipient } from '$lib/types';
import { PKG_URL, CRYPTIFY_URL } from '$lib/config';
const pg = new PostGuard({ pkgUrl: PKG_URL, cryptifyUrl: CRYPTIFY_URL });
export { pg };
export async function encryptAndSend(options: EncryptAndSendOptions): Promise<string> {
const { files, citizen, organisation, apiKey, message, onProgress, abortController } = options;
const sealed = pg.encrypt({
files,
recipients: [
pg.recipient.email(citizen.email),
pg.recipient.emailDomain(organisation.email)
],
sign: pg.sign.apiKey(apiKey),
onProgress,
signal: abortController?.signal
});
const result = await sealed.upload({
notify: {
message: message ?? undefined,
language: 'EN',
confirmToSender: false
}
});
return result.uuid;
}The server load function passes the API key to the page:
import { PG_API_KEY } from '$lib/config.server';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async () => {
return {
apiKey: PG_API_KEY
};
};The send page uses Svelte 5 reactive state to track progress and handle errors:
async function handleSend() {
if (!canSend) return;
sendState = 'encrypting';
progress = 0;
abortController = new AbortController();
try {
await encryptAndSend({
files,
citizen: { email: citizenEmail, name: citizenName },
organisation: { email: orgEmail, name: orgName },
apiKey,
message: message || null,
onProgress: (pct) => (progress = pct),
abortController
});
sendState = 'done';
} catch (e) {
if (abortController.signal.aborted) {
sendState = 'idle';
progress = 0;
} else {
sendState = 'error';
errorMessage = e instanceof Error ? e.message : String(e);
}
}
}Decrypt Files
A page that decrypts files from a Cryptify UUID. The UUID and recipient can come from URL query parameters (as provided in Cryptify notification emails):
import { PostGuard, IdentityMismatchError } from '@e4a/pg-js';
import type { DecryptFileResult } from '@e4a/pg-js';
import { PKG_URL, CRYPTIFY_URL } from '$lib/config';
const pg = new PostGuard({ pkgUrl: PKG_URL, cryptifyUrl: CRYPTIFY_URL });
async function startDecrypt() {
if (!uuid) {
uuid = manualUuid;
if (!uuid) return;
}
dlState = 'ready';
await tick();
try {
const opened = pg.open({ uuid });
const decrypted = await opened.decrypt({
element: '#yivi-web',
recipient: recipientParam || undefined
});
result = decrypted as DecryptFileResult;
senderEmail = result.sender?.email ?? '';
dlState = 'done';
result.download();
} catch (e) {
if (e instanceof IdentityMismatchError) {
dlState = 'identity-mismatch';
} else {
errorMessage = e instanceof Error ? e.message : String(e);
dlState = 'error';
}
}
}The template renders a Yivi QR container that the SDK populates:
<div id="yivi-web"></div>Yivi QR Styling
The Yivi QR container needs some CSS to render properly:
#yivi-qr, #yivi-web {
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}