Skip to content

Commit 0fb7789

Browse files
authored
feat(pg-node): add Node.js example using @e4a/pg-js (#46)
* feat(pg-node): add Node.js example using @e4a/pg-js Mirrors the pg-sveltekit "Informatierijk notificeren" flow (citizen exact-email recipient + organisation email-domain recipient) but as a plain Node CLI script — drop-in starting point for backend integrations that need to encrypt and upload from a server runtime. Two modes: npm run send # encrypt + upload + ask Cryptify to mail recipients npm run upload # encrypt + upload silently, return only the UUID Configuration via .env (loaded with Node's --env-file-if-exists). Defaults to staging; flip PG_CRYPTIFY_URL to production for real mail delivery. The package depends on @e4a/pg-js via "file:../../postguard-js" so local SDK changes are picked up immediately after a rebuild — useful while the non-browser-runtime support work is unreleased. Swap to a "^X.Y.Z" version once the SDK ships to npm. * chore(pg-node): depend on published @e4a/pg-js ^1.9.0 Swaps the temporary file:../../postguard-js link for the published ^1.9.0 release on npm. Bumps the example's engines.node to >=22 to match the SDK. README updated accordingly. Verified end-to-end against staging.postguard.eu — real UUID returned (bb90cb02-b27c-4b0d-a245-4acdb16056e1).
1 parent 35881a4 commit 0fb7789

9 files changed

Lines changed: 744 additions & 1 deletion

File tree

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
55
Example applications demonstrating PostGuard integration. Contains reference implementations for developers building on PostGuard. Code snippets in docs.postguard.eu come from this repo.
66

7-
There are three sub-projects:
7+
There are four sub-projects:
88

99
- `pg-sveltekit/`: SvelteKit web app using the `@e4a/pg-js` SDK.
10+
- `pg-node/`: Node.js CLI using the `@e4a/pg-js` SDK from a server runtime.
1011
- `pg-dotnet/`: .NET console app using the `postguard-dotnet` SDK.
1112
- `pg-manual/`: manual encryption/decryption using the `@e4a/pg-wasm` library directly.
1213

@@ -24,6 +25,19 @@ npm run dev
2425

2526
See [pg-sveltekit/README.md](pg-sveltekit/README.md) for environment variables and build instructions.
2627

28+
### Node.js example
29+
30+
Requires Node.js 20.6+ and a PostGuard API key.
31+
32+
```bash
33+
cd pg-node
34+
npm install
35+
cp .env.example .env # set at minimum PG_API_KEY
36+
npm run send # encrypt + upload + notify recipients
37+
```
38+
39+
See [pg-node/README.md](pg-node/README.md) for the full configuration and modes.
40+
2741
### .NET example
2842

2943
Requires the .NET 10.0+ SDK and a PostGuard API key. The example uses the `E4A.PostGuard` NuGet package, so no Rust toolchain or local build of the native library is needed.

pg-node/.env.example

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# PostGuard for Business API key (PG-…). Get one from
2+
# business.staging.postguard.eu (staging) or business.postguard.eu (production).
3+
PG_API_KEY=
4+
5+
# Endpoints. Defaults are staging.
6+
PG_PKG_URL=https://pkg.staging.postguard.eu
7+
PG_CRYPTIFY_URL=https://storage.staging.postguard.eu
8+
9+
# Optional: PostGuard website used in the printed download link.
10+
# Defaults to https://staging.postguard.eu on staging Cryptify,
11+
# else https://postguard.eu.
12+
# PG_DOWNLOAD_URL=https://staging.postguard.eu
13+
14+
# Recipients. Citizen is an exact email match; Organisation matches by email domain.
15+
PG_CITIZEN_EMAIL=citizen@example.com
16+
PG_ORGANISATION_EMAIL=noreply@example.org
17+
18+
# Optional unencrypted message body included in Cryptify's notification mail.
19+
PG_MESSAGE=
20+
21+
# Optional comma-separated list of file paths to encrypt. If empty the
22+
# example uses two in-memory demo files (report.txt + notes.txt).
23+
# PG_INPUT_FILES=./files/report.pdf,./files/notes.txt

pg-node/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
.env

pg-node/README.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# pg-node: PostGuard Node.js example
2+
3+
Node.js example demonstrating how to use the [@e4a/pg-js](https://www.npmjs.com/package/@e4a/pg-js) SDK from a server runtime. Mirrors the [pg-sveltekit](../pg-sveltekit) example's "Informatierijk notificeren" flow (citizen + organisation recipients) but as a CLI script — drop-in starting point for backend integrations.
4+
5+
## What it does
6+
7+
Two modes, selected by the script flag:
8+
9+
1. **Send** (`npm run send`) — encrypts the input files for a citizen (exact email) and an organisation (email domain), uploads to Cryptify, and asks Cryptify to email each recipient a download link.
10+
2. **Upload-only** (`npm run upload`) — same encryption + upload, but silent. Cryptify returns a UUID you can distribute through some other channel.
11+
12+
Files come from `PG_INPUT_FILES` (comma-separated paths) or two in-memory demo files if that is unset.
13+
14+
## Prerequisites
15+
16+
- **Node.js 22+** — matches `@e4a/pg-js`'s `engines.node`. The SDK also supports Bun and Deno; the same encryption code in `src/encryption.mjs` works there too.
17+
- A PostGuard for Business API key.
18+
19+
## Setup
20+
21+
```bash
22+
cd pg-node
23+
npm install
24+
cp .env.example .env
25+
# edit .env: set at minimum PG_API_KEY
26+
```
27+
28+
The `package.json` depends on the published [`@e4a/pg-js`](https://www.npmjs.com/package/@e4a/pg-js) (currently `^1.9.0`).
29+
30+
## Run
31+
32+
```bash
33+
npm run send # encrypt + upload + ask Cryptify to send mails
34+
npm run upload # encrypt + upload silently, no mails
35+
```
36+
37+
The script prints the resulting `uuid` and the corresponding `…/download?uuid=…` URL.
38+
39+
## Staging Cryptify does not send email
40+
41+
The default `PG_CRYPTIFY_URL` is `storage.staging.postguard.eu` — the staging deployment. It **does not actually deliver notification emails**, so you can exercise the full upload + notify flow without spamming real inboxes while you integrate.
42+
43+
- The upload itself works. You get back a real UUID and the download URL is usable.
44+
- `npm run send` succeeds, but no recipient mail is sent. Open the printed URL yourself to verify the decrypt flow end-to-end.
45+
- Point `PG_CRYPTIFY_URL` at the production Cryptify host to exercise real email delivery.
46+
47+
## Configuration
48+
49+
| Variable | Description | Default |
50+
| ----------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------- |
51+
| `PG_API_KEY` | PostGuard for Business API key (`PG-…`) | *(required)* |
52+
| `PG_PKG_URL` | PostGuard PKG server URL | `https://pkg.staging.postguard.eu` |
53+
| `PG_CRYPTIFY_URL` | Cryptify file-sharing URL | `https://storage.staging.postguard.eu` |
54+
| `PG_DOWNLOAD_URL` | PostGuard website used in `/download` URLs | `https://staging.postguard.eu` on staging Cryptify, else `https://postguard.eu` |
55+
| `PG_CITIZEN_EMAIL` | Citizen recipient (exact email match) | `citizen@example.com` |
56+
| `PG_ORGANISATION_EMAIL` | Organisation recipient (matches by domain) | `noreply@example.org` |
57+
| `PG_MESSAGE` | Optional unencrypted body for Cryptify's notify mail | *(empty)* |
58+
| `PG_INPUT_FILES` | Comma-separated file paths to encrypt | two in-memory demo files |
59+
60+
## How it maps to the SDK
61+
62+
The work happens in [`src/encryption.mjs`](./src/encryption.mjs):
63+
64+
```js
65+
const sealed = pg.encrypt({
66+
files,
67+
recipients: [pg.recipient.email(citizen.email), pg.recipient.emailDomain(organisation.email)],
68+
sign: pg.sign.apiKey(apiKey),
69+
onProgress,
70+
signal,
71+
});
72+
const { uuid } = await sealed.upload({ notify: { recipients: true, message, language: 'EN' } });
73+
```
74+
75+
`notify` must be nested under an object — the SDK validates the shape and throws a clear `TypeError` if you pass `{ notify: true }` or forget to nest. See the [SDK README](https://github.com/encryption4all/postguard-js#server-side-usage-node-bun-deno) for the full server-side surface.

pg-node/index.mjs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// CLI entry point. Run with one of:
2+
// npm run send # encrypts and asks Cryptify to mail recipients
3+
// npm run upload # encrypts and uploads silently
4+
// npm start # same as `npm run send`
5+
//
6+
// Configuration via .env or environment variables — see .env.example.
7+
8+
import { readFile } from 'node:fs/promises';
9+
import { basename } from 'node:path';
10+
import {
11+
API_KEY,
12+
CITIZEN_EMAIL,
13+
ORGANISATION_EMAIL,
14+
MESSAGE,
15+
INPUT_FILES,
16+
DOWNLOAD_URL,
17+
IS_CRYPTIFY_STAGING,
18+
CRYPTIFY_URL,
19+
PKG_URL,
20+
} from './src/config.mjs';
21+
import { encryptAndSend, encryptAndUpload } from './src/encryption.mjs';
22+
23+
if (!API_KEY) {
24+
console.error('Missing PG_API_KEY. Copy .env.example to .env and set it.');
25+
process.exit(2);
26+
}
27+
28+
const mode = process.argv.includes('--upload-only') ? 'upload-only' : 'send-email';
29+
30+
console.log(`pg-node example — mode: ${mode}`);
31+
console.log(` PKG: ${PKG_URL}`);
32+
console.log(` Cryptify: ${CRYPTIFY_URL}${IS_CRYPTIFY_STAGING ? ' (staging — no mails actually sent)' : ''}`);
33+
console.log(` Citizen: ${CITIZEN_EMAIL}`);
34+
console.log(` Organisation: ${ORGANISATION_EMAIL}`);
35+
console.log('');
36+
37+
const files = await loadFiles();
38+
console.log(`Encrypting ${files.length} file(s):`);
39+
for (const f of files) console.log(` ${f.name} (${f.size} bytes)`);
40+
console.log('');
41+
42+
const abortController = new AbortController();
43+
process.on('SIGINT', () => {
44+
console.log('\nCancelling…');
45+
abortController.abort();
46+
});
47+
48+
const onProgress = (pct) => {
49+
process.stdout.write(`\r upload progress: ${pct}% `);
50+
};
51+
52+
const t0 = performance.now();
53+
const uuid =
54+
mode === 'send-email'
55+
? await encryptAndSend({
56+
files,
57+
citizen: { email: CITIZEN_EMAIL },
58+
organisation: { email: ORGANISATION_EMAIL },
59+
apiKey: API_KEY,
60+
message: MESSAGE,
61+
onProgress,
62+
signal: abortController.signal,
63+
})
64+
: await encryptAndUpload({
65+
files,
66+
citizen: { email: CITIZEN_EMAIL },
67+
organisation: { email: ORGANISATION_EMAIL },
68+
apiKey: API_KEY,
69+
onProgress,
70+
signal: abortController.signal,
71+
});
72+
const t1 = performance.now();
73+
74+
process.stdout.write('\n');
75+
console.log('');
76+
console.log(`Done in ${(t1 - t0).toFixed(0)}ms`);
77+
console.log(`UUID: ${uuid}`);
78+
console.log(`Download: ${DOWNLOAD_URL}/download?uuid=${uuid}`);
79+
80+
if (mode === 'send-email' && IS_CRYPTIFY_STAGING) {
81+
console.log('');
82+
console.log('Note: staging Cryptify does not actually deliver mails. Open the URL above to test decrypt.');
83+
}
84+
85+
/** Read each path in PG_INPUT_FILES as a File. If empty, return two demo files. */
86+
async function loadFiles() {
87+
if (INPUT_FILES.length === 0) {
88+
return [
89+
new File(
90+
[new TextEncoder().encode('This is a sample report for PostGuard encryption testing.\n')],
91+
'report.txt',
92+
{ type: 'text/plain' }
93+
),
94+
new File(
95+
[new TextEncoder().encode('Confidential notes — only the intended recipient can read this.\n')],
96+
'notes.txt',
97+
{ type: 'text/plain' }
98+
),
99+
];
100+
}
101+
return Promise.all(
102+
INPUT_FILES.map(async (path) => {
103+
const bytes = await readFile(path);
104+
return new File([bytes], basename(path));
105+
})
106+
);
107+
}

0 commit comments

Comments
 (0)