@broadpaper/license 0.4.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -1
- package/README.md +8 -3
- package/dist/index.d.ts +56 -8
- package/dist/index.js +83 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -10,7 +10,7 @@ production use. The full agreement is the Terms and Conditions at
|
|
|
10
10
|
https://www.broadpaper.com/legal/terms/ ; this notice is the part of it you need
|
|
11
11
|
in the package, and where the two differ, those Terms prevail.
|
|
12
12
|
|
|
13
|
-
Version of
|
|
13
|
+
Version of 27 September 2026.
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
1. DEFINITIONS
|
|
@@ -25,6 +25,9 @@ and updates.
|
|
|
25
25
|
Organisation, and the rights granted with it. A Licence belongs to the
|
|
26
26
|
Organisation, not to an individual.
|
|
27
27
|
|
|
28
|
+
"Evaluation Licence" means a free, time-limited licence certificate we issue
|
|
29
|
+
under section 2. It is not a paid Licence.
|
|
30
|
+
|
|
28
31
|
"Product" means an application your Organisation builds that incorporates the
|
|
29
32
|
Software.
|
|
30
33
|
|
|
@@ -49,6 +52,17 @@ charge for it and whether or not the mark is present in the output. Removing,
|
|
|
49
52
|
suppressing or circumventing the mark does not make unlicensed production use
|
|
50
53
|
permitted.
|
|
51
54
|
|
|
55
|
+
We may issue your Organisation an Evaluation Licence, which lets the Software
|
|
56
|
+
generate documents without the evaluation mark for a limited period, currently
|
|
57
|
+
sixty days. It is part of this evaluation grant and gives no further rights: it
|
|
58
|
+
is for development, testing and evaluation only, and does not permit
|
|
59
|
+
production use. We issue at most one Evaluation Licence to each Organisation
|
|
60
|
+
and to each person, and we may refuse to issue one, or revoke one by telling
|
|
61
|
+
you, at our discretion. It ends on the expiry date it records, with no grace
|
|
62
|
+
period, after which documents carry the evaluation mark again. We may extend
|
|
63
|
+
one at our discretion by issuing a replacement. Section 3 does not apply to an
|
|
64
|
+
Evaluation Licence.
|
|
65
|
+
|
|
52
66
|
|
|
53
67
|
3. PAID LICENCES
|
|
54
68
|
|
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ Part of [**BroadPaper**](https://www.broadpaper.com/developers/?utm_source=npm&u
|
|
|
10
10
|
Your users design the report in your application; BroadPaper owns the layout, the branding,
|
|
11
11
|
the pagination and the output.
|
|
12
12
|
|
|
13
|
+
**[Try the demo](https://www.broadpaper.com/demo/?utm_source=npm&utm_medium=readme)** · **[Get a free evaluation key](https://www.broadpaper.com/eval/)** · [Pricing](https://www.broadpaper.com/developers/pricing/?utm_source=npm&utm_medium=readme)
|
|
14
|
+
|
|
13
15
|
## Install
|
|
14
16
|
|
|
15
17
|
```bash
|
|
@@ -41,11 +43,14 @@ Install it, use every feature and evaluate it against your real data for as long
|
|
|
41
43
|
Production use needs a licence.
|
|
42
44
|
|
|
43
45
|
In the software, the only difference without a licence is that generated PDFs carry an
|
|
44
|
-
evaluation mark
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
evaluation mark. A free 60-day [evaluation key](https://www.broadpaper.com/eval/) removes it
|
|
47
|
+
while you evaluate, and a paid licence removes it in production. Contractually, the paid
|
|
48
|
+
licence is also what permits production use. Evaluation keys need BroadPaper 1.0.0 or
|
|
49
|
+
later. No seats, no activation, no machine binding, no telemetry, and no call home at any
|
|
50
|
+
point.
|
|
47
51
|
|
|
48
52
|
- [How licensing works](https://www.broadpaper.com/docs/guide/licensing/)
|
|
53
|
+
- [Get a free evaluation key](https://www.broadpaper.com/eval/)
|
|
49
54
|
- [Pricing](https://www.broadpaper.com/developers/pricing/?utm_source=npm&utm_medium=readme)
|
|
50
55
|
- [Licence agreement](https://www.broadpaper.com/legal/terms/)
|
|
51
56
|
|
package/dist/index.d.ts
CHANGED
|
@@ -18,7 +18,20 @@
|
|
|
18
18
|
* that quotes a different vocabulary from the thing that priced it is a
|
|
19
19
|
* certificate somebody eventually has to write a mapping table for.
|
|
20
20
|
*/
|
|
21
|
-
type Plan = "internal" | "saas";
|
|
21
|
+
type Plan = "internal" | "saas" | "evaluation";
|
|
22
|
+
/**
|
|
23
|
+
* The free, time-limited licence a developer gets from their account in
|
|
24
|
+
* exchange for telling us who they are.
|
|
25
|
+
*
|
|
26
|
+
* It removes the mark while somebody evaluates, and nothing else: it permits no
|
|
27
|
+
* production use, and it has no grace period — the day it ends, the mark comes
|
|
28
|
+
* back. It is not in `shared/plans.json`, because nothing sells it.
|
|
29
|
+
*
|
|
30
|
+
* SDKs before 1.0 do not know the word and report such a licence as
|
|
31
|
+
* `unsupported`, which keeps the mark and says to upgrade. That is the fail-safe
|
|
32
|
+
* direction, and `tests/site/licence-format.test.ts` holds 0.4.1 to it.
|
|
33
|
+
*/
|
|
34
|
+
declare const EVALUATION_PLAN = "evaluation";
|
|
22
35
|
/**
|
|
23
36
|
* How the licence is held.
|
|
24
37
|
*
|
|
@@ -142,6 +155,13 @@ interface LicenseStatus {
|
|
|
142
155
|
* `active`, so callers can log it without composing their own prose.
|
|
143
156
|
*/
|
|
144
157
|
message: string | null;
|
|
158
|
+
/**
|
|
159
|
+
* An informational line for a licence that *is* being honoured but is worth
|
|
160
|
+
* knowing about — today, only an evaluation licence, which says when it ends.
|
|
161
|
+
* Logged at info level, once. Optional so a status built by hand elsewhere
|
|
162
|
+
* still type-checks.
|
|
163
|
+
*/
|
|
164
|
+
notice?: string | null;
|
|
145
165
|
}
|
|
146
166
|
/** A public key this build of the SDK will accept signatures from. */
|
|
147
167
|
interface TrustedKey {
|
|
@@ -162,12 +182,17 @@ interface TrustedKey {
|
|
|
162
182
|
* term.
|
|
163
183
|
*/
|
|
164
184
|
declare const DEFAULT_GRACE_PERIOD_DAYS = 30;
|
|
185
|
+
/** Where a developer gets a free evaluation key. Printed, never fetched. */
|
|
186
|
+
declare const EVALUATION_KEY_URL = "https://www.broadpaper.com/eval";
|
|
165
187
|
interface VerifyOptions {
|
|
166
188
|
/** Fixed clock. Tests and deterministic renders pass one. */
|
|
167
189
|
now?: Date;
|
|
168
190
|
/** Extra keys to trust, on top of the ones built into this SDK. */
|
|
169
191
|
trustedKeys?: readonly TrustedKey[];
|
|
170
|
-
/**
|
|
192
|
+
/**
|
|
193
|
+
* Overrides {@link DEFAULT_GRACE_PERIOD_DAYS}. Never applies to an evaluation
|
|
194
|
+
* licence, which has no grace period whatever a host configures.
|
|
195
|
+
*/
|
|
171
196
|
gracePeriodDays?: number;
|
|
172
197
|
}
|
|
173
198
|
/** The status of a caller that supplied no licence at all. */
|
|
@@ -217,9 +242,15 @@ interface BroadPaperConfig {
|
|
|
217
242
|
trustedKeys?: readonly TrustedKey[];
|
|
218
243
|
/** Overrides the 30-day post-expiry grace period. Mostly for tests. */
|
|
219
244
|
gracePeriodDays?: number;
|
|
220
|
-
/**
|
|
221
|
-
|
|
245
|
+
/**
|
|
246
|
+
* Where licence messages go. Defaults to `console.warn` for warnings and
|
|
247
|
+
* `console.info` for information; `null` silences both. The level is a
|
|
248
|
+
* second argument so a logger written for earlier versions still works.
|
|
249
|
+
*/
|
|
250
|
+
logger?: ((message: string, level: LogLevel) => void) | null;
|
|
222
251
|
}
|
|
252
|
+
/** How loudly a licence message is said. */
|
|
253
|
+
type LogLevel = "info" | "warn";
|
|
223
254
|
/**
|
|
224
255
|
* Sets the licence and any other SDK-wide options. Call it once, at startup.
|
|
225
256
|
*
|
|
@@ -241,6 +272,8 @@ declare const BroadPaper: {
|
|
|
241
272
|
};
|
|
242
273
|
/** Logs a message once, whatever it takes to reach the developer. */
|
|
243
274
|
declare function warn(message: string): void;
|
|
275
|
+
/** Logs an informational message once. */
|
|
276
|
+
declare function info(message: string): void;
|
|
244
277
|
interface ResolveOptions extends VerifyOptions {
|
|
245
278
|
/** A licence for this call only, overriding the configured one. */
|
|
246
279
|
license?: string | null;
|
|
@@ -370,12 +403,27 @@ interface WatermarkSpec {
|
|
|
370
403
|
*
|
|
371
404
|
* For a host's own credit line, whose job is to be followed: a free document a
|
|
372
405
|
* business sends a client is the most-read advert the host will ever have, and
|
|
373
|
-
* an address nobody can click is one hardly anybody types. The evaluation
|
|
374
|
-
* never has one
|
|
375
|
-
*
|
|
406
|
+
* an address nobody can click is one hardly anybody types. The evaluation
|
|
407
|
+
* stamp never has one, because it is there to be seen, not visited; its
|
|
408
|
+
* footnote does, because saying where to get a key is the footnote's whole job.
|
|
376
409
|
*/
|
|
377
410
|
href?: string;
|
|
411
|
+
/**
|
|
412
|
+
* A second mark drawn with this one: a `bottom` line under a `center` stamp.
|
|
413
|
+
*
|
|
414
|
+
* The evaluation mark carries one that says how to remove it. It is a field
|
|
415
|
+
* of the spec rather than a second return value of `watermarkFor` so that
|
|
416
|
+
* everything already passing one spec around — Cloud, a host's own extra mark,
|
|
417
|
+
* both backends — carries it without learning a new shape. Backends reach it
|
|
418
|
+
* through {@link expandWatermarks}.
|
|
419
|
+
*/
|
|
420
|
+
footnote?: WatermarkSpec;
|
|
378
421
|
}
|
|
422
|
+
/**
|
|
423
|
+
* The marks to draw, with each spec's `footnote` beside it and the empty
|
|
424
|
+
* slots dropped. What every backend iterates.
|
|
425
|
+
*/
|
|
426
|
+
declare function expandWatermarks(marks: ReadonlyArray<WatermarkSpec | null | undefined>): WatermarkSpec[];
|
|
379
427
|
/**
|
|
380
428
|
* The mark itself.
|
|
381
429
|
*
|
|
@@ -461,4 +509,4 @@ declare function bytesToBase64Url(bytes: Uint8Array): string;
|
|
|
461
509
|
/** Throws on anything that is not base64/base64url. Padding is optional. */
|
|
462
510
|
declare function base64UrlToBytes(text: string): Uint8Array;
|
|
463
511
|
|
|
464
|
-
export { BroadPaper, type BroadPaperConfig, DEFAULT_GRACE_PERIOD_DAYS, EVALUATION_WATERMARK, FORMAT_PREFIX, LicenseFormatError, type LicensePayload, type LicenseState, type LicenseStatus, type LicenseType, PRODUCT, type ParsedLicense, type Plan, type ResolveOptions, TRUSTED_KEYS, type TrustedKey, type VerifyOptions, type WatermarkSpec, base64UrlToBytes, bytesToBase64Url, configure, encodeSigningInput, formatLicense, getConfig, normalizeLicense, parseLicense, producerString, resetConfig, resolveLicenseStatus, resolveWatermark, unlicensed, verifyLicense, warn, watermarkCss, watermarkFor };
|
|
512
|
+
export { BroadPaper, type BroadPaperConfig, DEFAULT_GRACE_PERIOD_DAYS, EVALUATION_KEY_URL, EVALUATION_PLAN, EVALUATION_WATERMARK, FORMAT_PREFIX, LicenseFormatError, type LicensePayload, type LicenseState, type LicenseStatus, type LicenseType, type LogLevel, PRODUCT, type ParsedLicense, type Plan, type ResolveOptions, TRUSTED_KEYS, type TrustedKey, type VerifyOptions, type WatermarkSpec, base64UrlToBytes, bytesToBase64Url, configure, encodeSigningInput, expandWatermarks, formatLicense, getConfig, info, normalizeLicense, parseLicense, producerString, resetConfig, resolveLicenseStatus, resolveWatermark, unlicensed, verifyLicense, warn, watermarkCss, watermarkFor };
|
package/dist/index.js
CHANGED
|
@@ -72,7 +72,7 @@ var LicenseFormatError = class extends Error {
|
|
|
72
72
|
}
|
|
73
73
|
kind;
|
|
74
74
|
};
|
|
75
|
-
var PLANS = ["internal", "saas"];
|
|
75
|
+
var PLANS = ["internal", "saas", "evaluation"];
|
|
76
76
|
var LICENSE_TYPES = ["subscription"];
|
|
77
77
|
function normalizeLicense(license) {
|
|
78
78
|
return license.replace(/\s+/g, "");
|
|
@@ -144,6 +144,9 @@ function readPayload(raw) {
|
|
|
144
144
|
if (!PLANS.includes(plan)) {
|
|
145
145
|
throw new LicenseFormatError(`this version of BroadPaper does not know the plan ${JSON.stringify(plan)}`, "unsupported");
|
|
146
146
|
}
|
|
147
|
+
if (plan === "evaluation" && o.validUntil === null) {
|
|
148
|
+
throw new LicenseFormatError("an evaluation licence must have an end date");
|
|
149
|
+
}
|
|
147
150
|
const licenseType = text("licenseType");
|
|
148
151
|
if (!LICENSE_TYPES.includes(licenseType)) {
|
|
149
152
|
throw new LicenseFormatError(`this version of BroadPaper cannot honour a ${JSON.stringify(licenseType)} licence`, "unsupported");
|
|
@@ -179,10 +182,12 @@ var TRUSTED_KEYS = [
|
|
|
179
182
|
];
|
|
180
183
|
|
|
181
184
|
// src/types.ts
|
|
185
|
+
var EVALUATION_PLAN = "evaluation";
|
|
182
186
|
var PRODUCT = "broadpaper-sdk";
|
|
183
187
|
|
|
184
188
|
// src/verify.ts
|
|
185
189
|
var DEFAULT_GRACE_PERIOD_DAYS = 30;
|
|
190
|
+
var EVALUATION_KEY_URL = "https://www.broadpaper.com/eval";
|
|
186
191
|
var DAY_MS = 864e5;
|
|
187
192
|
function unlicensed() {
|
|
188
193
|
return {
|
|
@@ -192,7 +197,7 @@ function unlicensed() {
|
|
|
192
197
|
plan: null,
|
|
193
198
|
validUntil: null,
|
|
194
199
|
daysRemaining: null,
|
|
195
|
-
message:
|
|
200
|
+
message: `BroadPaper is running in evaluation mode. Get a free evaluation key: ${EVALUATION_KEY_URL}`
|
|
196
201
|
};
|
|
197
202
|
}
|
|
198
203
|
function failure(state, message, payload = null) {
|
|
@@ -250,7 +255,9 @@ async function verifyLicense(license, options = {}) {
|
|
|
250
255
|
return { state: "active", licensed: true, payload, plan: payload.plan, validUntil: null, daysRemaining: null, message: null };
|
|
251
256
|
}
|
|
252
257
|
const validUntil = new Date(payload.validUntil * 1e3);
|
|
253
|
-
const
|
|
258
|
+
const evaluation = payload.plan === EVALUATION_PLAN;
|
|
259
|
+
const graceDays = evaluation ? 0 : options.gracePeriodDays ?? DEFAULT_GRACE_PERIOD_DAYS;
|
|
260
|
+
const day = validUntil.toISOString().slice(0, 10);
|
|
254
261
|
const msRemaining = validUntil.getTime() - now.getTime();
|
|
255
262
|
const daysRemaining = Math.floor(msRemaining / DAY_MS);
|
|
256
263
|
if (msRemaining > 0) {
|
|
@@ -261,9 +268,17 @@ async function verifyLicense(license, options = {}) {
|
|
|
261
268
|
plan: payload.plan,
|
|
262
269
|
validUntil,
|
|
263
270
|
daysRemaining,
|
|
264
|
-
message: null
|
|
271
|
+
message: null,
|
|
272
|
+
notice: evaluation ? `BroadPaper is running on an evaluation licence for ${payload.organisationName}, which ends on ${day}. It is for evaluation and development, not production use.` : null
|
|
265
273
|
};
|
|
266
274
|
}
|
|
275
|
+
if (evaluation) {
|
|
276
|
+
return failure(
|
|
277
|
+
"expired",
|
|
278
|
+
`The BroadPaper evaluation licence for ${payload.organisationName} ended on ${day}, so PDFs carry the evaluation watermark again. Need longer? Reply to your licence email or write to hello@broadpaper.com.`,
|
|
279
|
+
payload
|
|
280
|
+
);
|
|
281
|
+
}
|
|
267
282
|
if (msRemaining > -graceDays * DAY_MS) {
|
|
268
283
|
const daysOver = Math.max(1, Math.ceil(-msRemaining / DAY_MS));
|
|
269
284
|
return {
|
|
@@ -278,7 +293,7 @@ async function verifyLicense(license, options = {}) {
|
|
|
278
293
|
}
|
|
279
294
|
return failure(
|
|
280
295
|
"expired",
|
|
281
|
-
`The BroadPaper licence for ${payload.organisationName} expired on ${
|
|
296
|
+
`The BroadPaper licence for ${payload.organisationName} expired on ${day} and its ${graceDays}-day grace period has passed, so PDFs carry the evaluation watermark. Renewing issues a new licence in your BroadPaper account.`,
|
|
282
297
|
payload
|
|
283
298
|
);
|
|
284
299
|
}
|
|
@@ -333,15 +348,26 @@ function trustedKeysFromEnv() {
|
|
|
333
348
|
}
|
|
334
349
|
}
|
|
335
350
|
function warn(message) {
|
|
351
|
+
log(message, "warn");
|
|
352
|
+
}
|
|
353
|
+
function info(message) {
|
|
354
|
+
log(message, "info");
|
|
355
|
+
}
|
|
356
|
+
function log(message, level) {
|
|
336
357
|
const s = store();
|
|
337
358
|
if (s.logged.has(message)) return;
|
|
338
359
|
s.logged.add(message);
|
|
339
360
|
const logger = s.config.logger;
|
|
340
361
|
if (logger === null) return;
|
|
341
362
|
const line = `[BroadPaper] ${message}`;
|
|
342
|
-
if (logger) logger(line);
|
|
363
|
+
if (logger) logger(line, level);
|
|
364
|
+
else if (level === "info") console.info(line);
|
|
343
365
|
else console.warn(line);
|
|
344
366
|
}
|
|
367
|
+
function report(status) {
|
|
368
|
+
if (status.message) warn(status.message);
|
|
369
|
+
if (status.notice) info(status.notice);
|
|
370
|
+
}
|
|
345
371
|
async function resolveLicenseStatus(options = {}) {
|
|
346
372
|
const s = store();
|
|
347
373
|
const config = s.config;
|
|
@@ -350,11 +376,11 @@ async function resolveLicenseStatus(options = {}) {
|
|
|
350
376
|
const gracePeriodDays = options.gracePeriodDays ?? config.gracePeriodDays;
|
|
351
377
|
if (!license) {
|
|
352
378
|
const status = unlicensed();
|
|
353
|
-
|
|
379
|
+
report(status);
|
|
354
380
|
return status;
|
|
355
381
|
}
|
|
356
382
|
const verify = () => verifyLicense(license, { ...options.now ? { now: options.now } : {}, trustedKeys, ...gracePeriodDays !== void 0 ? { gracePeriodDays } : {} }).then((status) => {
|
|
357
|
-
|
|
383
|
+
report(status);
|
|
358
384
|
return status;
|
|
359
385
|
});
|
|
360
386
|
if (options.now || options.license !== void 0) return verify();
|
|
@@ -364,12 +390,38 @@ async function resolveLicenseStatus(options = {}) {
|
|
|
364
390
|
}
|
|
365
391
|
|
|
366
392
|
// src/watermark.ts
|
|
393
|
+
function expandWatermarks(marks) {
|
|
394
|
+
const out = [];
|
|
395
|
+
for (const mark of marks) {
|
|
396
|
+
if (!mark) continue;
|
|
397
|
+
out.push(mark);
|
|
398
|
+
if (mark.footnote) out.push(mark.footnote);
|
|
399
|
+
}
|
|
400
|
+
return out;
|
|
401
|
+
}
|
|
367
402
|
var EVALUATION_WATERMARK = {
|
|
368
403
|
text: "BroadPaper Evaluation Copy",
|
|
369
404
|
fontSize: 44,
|
|
370
405
|
angle: -35,
|
|
371
406
|
color: "#c8102e",
|
|
372
|
-
opacity: 0.12
|
|
407
|
+
opacity: 0.12,
|
|
408
|
+
/*
|
|
409
|
+
* The line that says what to do about the stamp. The stamp itself is never a
|
|
410
|
+
* link — it is there to be seen. This line is the opposite: it exists to be
|
|
411
|
+
* followed, so the Forme backend links it; Chromium stamps text into a finished
|
|
412
|
+
* file and draws it unlinked. Darker than the stamp because at eight points
|
|
413
|
+
* the stamp's opacity is unreadable, and still grey enough to leave the page
|
|
414
|
+
* a page. A full stop rather than a dash, for the reason above.
|
|
415
|
+
*/
|
|
416
|
+
footnote: {
|
|
417
|
+
text: "Evaluation copy. Remove this mark with a free key: broadpaper.com/eval",
|
|
418
|
+
fontSize: 8,
|
|
419
|
+
angle: 0,
|
|
420
|
+
color: "#6b6b6b",
|
|
421
|
+
opacity: 0.9,
|
|
422
|
+
placement: "bottom",
|
|
423
|
+
href: "https://www.broadpaper.com/eval"
|
|
424
|
+
}
|
|
373
425
|
};
|
|
374
426
|
async function resolveWatermark(options = {}) {
|
|
375
427
|
const status = await resolveLicenseStatus(options);
|
|
@@ -379,11 +431,11 @@ function watermarkFor(status) {
|
|
|
379
431
|
return status.licensed ? null : EVALUATION_WATERMARK;
|
|
380
432
|
}
|
|
381
433
|
function watermarkCss(spec = EVALUATION_WATERMARK) {
|
|
382
|
-
const
|
|
434
|
+
const note = spec.footnote;
|
|
383
435
|
return [
|
|
384
436
|
".bp-page{position:relative;}",
|
|
385
437
|
".bp-page::after{",
|
|
386
|
-
`content:"${text}";`,
|
|
438
|
+
`content:"${cssString(spec.text)}";`,
|
|
387
439
|
"position:absolute;inset:0;",
|
|
388
440
|
"display:flex;align-items:center;justify-content:center;",
|
|
389
441
|
"pointer-events:none;z-index:2147483647;",
|
|
@@ -391,9 +443,24 @@ function watermarkCss(spec = EVALUATION_WATERMARK) {
|
|
|
391
443
|
"font-family:Helvetica,Arial,sans-serif;font-weight:700;letter-spacing:0.04em;white-space:nowrap;",
|
|
392
444
|
`font-size:${spec.fontSize}pt;color:${spec.color};opacity:${spec.opacity};`,
|
|
393
445
|
"-webkit-print-color-adjust:exact;print-color-adjust:exact;",
|
|
394
|
-
"}"
|
|
446
|
+
"}",
|
|
447
|
+
// The footnote, along the foot of the same page box. Unlinked here: a
|
|
448
|
+
// pseudo-element cannot be an anchor.
|
|
449
|
+
...note ? [
|
|
450
|
+
".bp-page::before{",
|
|
451
|
+
`content:"${cssString(note.text)}";`,
|
|
452
|
+
"position:absolute;left:0;right:0;bottom:4pt;text-align:center;",
|
|
453
|
+
"pointer-events:none;z-index:2147483647;",
|
|
454
|
+
"font-family:Helvetica,Arial,sans-serif;white-space:nowrap;",
|
|
455
|
+
`font-size:${note.fontSize}pt;color:${note.color};opacity:${note.opacity};`,
|
|
456
|
+
"-webkit-print-color-adjust:exact;print-color-adjust:exact;",
|
|
457
|
+
"}"
|
|
458
|
+
] : []
|
|
395
459
|
].join("");
|
|
396
460
|
}
|
|
461
|
+
function cssString(text) {
|
|
462
|
+
return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
463
|
+
}
|
|
397
464
|
function producerString(status) {
|
|
398
465
|
if (!status.licensed || !status.payload) return "BroadPaper (evaluation)";
|
|
399
466
|
return `BroadPaper (${status.payload.licenseId})`;
|
|
@@ -401,6 +468,8 @@ function producerString(status) {
|
|
|
401
468
|
export {
|
|
402
469
|
BroadPaper,
|
|
403
470
|
DEFAULT_GRACE_PERIOD_DAYS,
|
|
471
|
+
EVALUATION_KEY_URL,
|
|
472
|
+
EVALUATION_PLAN,
|
|
404
473
|
EVALUATION_WATERMARK,
|
|
405
474
|
FORMAT_PREFIX,
|
|
406
475
|
LicenseFormatError,
|
|
@@ -410,8 +479,10 @@ export {
|
|
|
410
479
|
bytesToBase64Url,
|
|
411
480
|
configure,
|
|
412
481
|
encodeSigningInput,
|
|
482
|
+
expandWatermarks,
|
|
413
483
|
formatLicense,
|
|
414
484
|
getConfig,
|
|
485
|
+
info,
|
|
415
486
|
normalizeLicense,
|
|
416
487
|
parseLicense,
|
|
417
488
|
producerString,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/verify.ts","../src/encoding.ts","../src/format.ts","../src/trusted-keys.ts","../src/types.ts","../src/config.ts","../src/watermark.ts"],"sourcesContent":["/**\n * Offline licence verification.\n *\n * Everything here is local: a signature check against a compiled-in public key\n * and a comparison against the clock. There is no activation call, no licence\n * server and nothing to be offline from. That is a deliberate product decision\n * as much as a technical one — a report renderer that stops working because a\n * key server is down is a report renderer nobody puts in a payroll run.\n *\n * The failure modes are all *soft*. An unreadable licence never throws out of a\n * render; it produces a status whose `message` says what is wrong, and the PDF\n * comes out carrying the evaluation mark.\n */\nimport { verifyAsync } from \"@noble/ed25519\";\nimport { base64UrlToBytes, utf8ToBytes } from \"./encoding.js\";\nimport { LicenseFormatError, parseLicense } from \"./format.js\";\nimport { TRUSTED_KEYS } from \"./trusted-keys.js\";\nimport { PRODUCT, type LicensePayload, type LicenseStatus, type TrustedKey } from \"./types.js\";\n\n/**\n * How long an expired licence keeps working.\n *\n * A renewal that is a week late is an accounts payable problem, not a reason to\n * stamp a customer's month-end reports. Thirty days is long enough to cover a\n * card expiring over a holiday and short enough that it is not a second licence\n * term.\n */\nexport const DEFAULT_GRACE_PERIOD_DAYS = 30;\n\nconst DAY_MS = 86_400_000;\n\nexport interface VerifyOptions {\n /** Fixed clock. Tests and deterministic renders pass one. */\n now?: Date;\n /** Extra keys to trust, on top of the ones built into this SDK. */\n trustedKeys?: readonly TrustedKey[];\n /** Overrides {@link DEFAULT_GRACE_PERIOD_DAYS}. */\n gracePeriodDays?: number;\n}\n\n/** The status of a caller that supplied no licence at all. */\nexport function unlicensed(): LicenseStatus {\n return {\n state: \"unlicensed\",\n licensed: false,\n payload: null,\n plan: null,\n validUntil: null,\n daysRemaining: null,\n message:\n \"No BroadPaper licence is configured, so PDFs carry the evaluation watermark. Everything else works; see https://www.broadpaper.com/docs/guide/licensing.\"\n };\n}\n\nfunction failure(state: LicenseStatus[\"state\"], message: string, payload: LicensePayload | null = null): LicenseStatus {\n return {\n state,\n licensed: false,\n payload,\n plan: null,\n validUntil: payload?.validUntil != null ? new Date(payload.validUntil * 1000) : null,\n daysRemaining: null,\n message\n };\n}\n\n/**\n * Checks a licence string and reports what it entitles the caller to.\n *\n * Never throws for a bad licence: a malformed string, a forged signature and an\n * expired term are all ordinary answers, and a render is not the place to\n * discover that a config file has a typo in it.\n */\nexport async function verifyLicense(license: string | null | undefined, options: VerifyOptions = {}): Promise<LicenseStatus> {\n if (!license || !license.trim()) return unlicensed();\n\n let parsed;\n try {\n parsed = parseLicense(license);\n } catch (e) {\n if (e instanceof LicenseFormatError) {\n return failure(\n e.kind,\n e.kind === \"unsupported\"\n ? `This BroadPaper licence needs a newer version of the SDK (${e.message}). Update @broadpaper/* or the PDF will carry the evaluation watermark.`\n : `The BroadPaper licence could not be read (${e.message}), so PDFs carry the evaluation watermark.`\n );\n }\n /* c8 ignore next -- parseLicense only throws LicenseFormatError */\n throw e;\n }\n\n const { payload } = parsed;\n if (payload.product !== PRODUCT) {\n return failure(\n \"invalid\",\n `This licence is for ${JSON.stringify(payload.product)}, not the BroadPaper SDK, so PDFs carry the evaluation watermark.`,\n payload\n );\n }\n\n const key = findKey(payload.keyId, options.trustedKeys);\n if (!key) {\n return failure(\n \"untrusted\",\n `This BroadPaper licence was signed with key ${JSON.stringify(payload.keyId)}, which this build does not trust. ` +\n \"A licence issued for development is not honoured by a production build; check BROADPAPER_LICENSE points at the right one.\",\n payload\n );\n }\n\n let signatureOk = false;\n try {\n signatureOk = await verifyAsync(parsed.signature, utf8ToBytes(parsed.signedText), base64UrlToBytes(key.publicKey));\n } catch {\n // A malformed public key or a point that is not on the curve. Either way\n // this licence is not going to verify, and a render must not fail over it.\n signatureOk = false;\n }\n if (!signatureOk) {\n return failure(\"invalid\", \"This BroadPaper licence failed its signature check — it has been altered or truncated. PDFs carry the evaluation watermark.\", payload);\n }\n\n const now = options.now ?? new Date();\n\n // A licence with no end date never expires, so there is no clock to consult.\n // Nothing sold today issues one — `readPayload` refuses the only licence type\n // that would — but the field is nullable, and a null that fell through to the\n // arithmetic below would come out as 1970 and watermark a valid licence.\n if (payload.validUntil === null) {\n return { state: \"active\", licensed: true, payload, plan: payload.plan, validUntil: null, daysRemaining: null, message: null };\n }\n\n const validUntil = new Date(payload.validUntil * 1000);\n const graceDays = options.gracePeriodDays ?? DEFAULT_GRACE_PERIOD_DAYS;\n const msRemaining = validUntil.getTime() - now.getTime();\n const daysRemaining = Math.floor(msRemaining / DAY_MS);\n\n if (msRemaining > 0) {\n return {\n state: \"active\",\n licensed: true,\n payload,\n plan: payload.plan,\n validUntil,\n daysRemaining,\n message: null\n };\n }\n\n if (msRemaining > -graceDays * DAY_MS) {\n const daysOver = Math.max(1, Math.ceil(-msRemaining / DAY_MS));\n return {\n state: \"grace\",\n licensed: true,\n payload,\n plan: payload.plan,\n validUntil,\n daysRemaining,\n message:\n `The BroadPaper licence for ${payload.organisationName} expired ${daysOver} day${daysOver === 1 ? \"\" : \"s\"} ago and is inside its ${graceDays}-day grace period. ` +\n \"Renew it and copy the new licence from your account, or PDFs will start carrying the evaluation watermark.\"\n };\n }\n\n return failure(\n \"expired\",\n `The BroadPaper licence for ${payload.organisationName} expired on ${validUntil.toISOString().slice(0, 10)} and its ${graceDays}-day grace period has passed, ` +\n \"so PDFs carry the evaluation watermark. Renewing issues a new licence in your BroadPaper account.\",\n payload\n );\n}\n\nfunction findKey(keyId: string, extra: readonly TrustedKey[] | undefined): TrustedKey | undefined {\n // Runtime keys are searched first so a development key can shadow a built-in\n // one of the same name without a rebuild.\n return extra?.find((k) => k.keyId === keyId) ?? TRUSTED_KEYS.find((k) => k.keyId === keyId);\n}\n","/**\n * base64url and UTF-8, without depending on Node or on the DOM.\n *\n * The SDK runs in Node, in edge runtimes, in a worker and in a browser, and the\n * licence string has to decode identically in all four. `atob`/`btoa` exist\n * everywhere modern but are byte-oriented and mangle anything above U+00FF, so\n * text goes through `TextEncoder`/`TextDecoder` first and only bytes are\n * base64'd. The tables below are the whole implementation, which is smaller\n * than the runtime detection needed to use `Buffer` where it happens to exist.\n */\n\nconst ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\nconst LOOKUP = /* @__PURE__ */ (() => {\n const t = new Int8Array(128).fill(-1);\n for (let i = 0; i < ALPHABET.length; i++) t[ALPHABET.charCodeAt(i)] = i;\n // Accept standard base64 as well, so a licence that survived a round trip\n // through a system that rewrote the alphabet still reads.\n t[\"+\".charCodeAt(0)] = 62;\n t[\"/\".charCodeAt(0)] = 63;\n return t;\n})();\n\nexport function bytesToBase64Url(bytes: Uint8Array): string {\n let out = \"\";\n let i = 0;\n for (; i + 2 < bytes.length; i += 3) {\n const n = (bytes[i]! << 16) | (bytes[i + 1]! << 8) | bytes[i + 2]!;\n out += ALPHABET[(n >>> 18) & 63]! + ALPHABET[(n >>> 12) & 63]! + ALPHABET[(n >>> 6) & 63]! + ALPHABET[n & 63]!;\n }\n const rest = bytes.length - i;\n if (rest === 1) {\n const n = bytes[i]! << 16;\n out += ALPHABET[(n >>> 18) & 63]! + ALPHABET[(n >>> 12) & 63]!;\n } else if (rest === 2) {\n const n = (bytes[i]! << 16) | (bytes[i + 1]! << 8);\n out += ALPHABET[(n >>> 18) & 63]! + ALPHABET[(n >>> 12) & 63]! + ALPHABET[(n >>> 6) & 63]!;\n }\n return out;\n}\n\n/** Throws on anything that is not base64/base64url. Padding is optional. */\nexport function base64UrlToBytes(text: string): Uint8Array {\n let end = text.length;\n while (end > 0 && text[end - 1] === \"=\") end--;\n const chars: number[] = [];\n for (let i = 0; i < end; i++) {\n const code = text.charCodeAt(i);\n const value = code < 128 ? LOOKUP[code]! : -1;\n if (value < 0) throw new Error(`not base64: ${JSON.stringify(text[i])} at ${i}`);\n chars.push(value);\n }\n // 4 base64 characters carry 3 bytes; a group of 1 is never valid.\n if (chars.length % 4 === 1) throw new Error(\"not base64: truncated\");\n const out = new Uint8Array(Math.floor((chars.length * 3) / 4));\n let o = 0;\n let i = 0;\n for (; i + 3 < chars.length; i += 4) {\n const n = (chars[i]! << 18) | (chars[i + 1]! << 12) | (chars[i + 2]! << 6) | chars[i + 3]!;\n out[o++] = (n >>> 16) & 255;\n out[o++] = (n >>> 8) & 255;\n out[o++] = n & 255;\n }\n const rest = chars.length - i;\n if (rest === 2) {\n out[o++] = ((chars[i]! << 18) | (chars[i + 1]! << 12)) >>> 16;\n } else if (rest === 3) {\n const n = (chars[i]! << 18) | (chars[i + 1]! << 12) | (chars[i + 2]! << 6);\n out[o++] = (n >>> 16) & 255;\n out[o++] = (n >>> 8) & 255;\n }\n return out;\n}\n\nconst encoder = /* @__PURE__ */ new TextEncoder();\nconst decoder = /* @__PURE__ */ new TextDecoder(\"utf-8\", { fatal: true });\n\nexport const utf8ToBytes = (text: string): Uint8Array => encoder.encode(text);\nexport const bytesToUtf8 = (bytes: Uint8Array): string => decoder.decode(bytes);\n","/**\n * The licence string.\n *\n * BP1.<base64url payload>.<base64url signature>\n *\n * Three parts, dot-separated, safe in an environment variable, a YAML file, a\n * URL and a shell argument without quoting. The leading `BP1` is the *string\n * format* version and `payload.version` is the *payload schema* version; they\n * move independently, and both are checked, so a licence issued for a later\n * format is reported as unsupported rather than as corrupt.\n *\n * What is signed is the bytes of `BP1.<payload>` — prefix included. Signing the\n * encoded text rather than the decoded object means verification never depends\n * on two JSON serialisers agreeing about key order or number formatting, and\n * covering the prefix stops a payload being lifted into a future format that\n * might read a field differently.\n */\nimport { base64UrlToBytes, bytesToBase64Url, bytesToUtf8, utf8ToBytes } from \"./encoding.js\";\nimport { PRODUCT, type LicensePayload, type LicenseType, type Plan } from \"./types.js\";\n\n/** The only string format this SDK can read. */\nexport const FORMAT_PREFIX = \"BP1\";\n\n/** A licence string split into its parts, before any signature check. */\nexport interface ParsedLicense {\n prefix: string;\n /** The exact text that was signed. */\n signedText: string;\n encodedPayload: string;\n signature: Uint8Array;\n payload: LicensePayload;\n}\n\nexport class LicenseFormatError extends Error {\n constructor(\n message: string,\n /** `unsupported` when the licence is well-formed but newer than this SDK. */\n readonly kind: \"invalid\" | \"unsupported\" = \"invalid\"\n ) {\n super(message);\n this.name = \"LicenseFormatError\";\n }\n}\n\nconst PLANS: readonly Plan[] = [\"internal\", \"saas\"];\n\n/**\n * The licence types this SDK can actually honour.\n *\n * Only `subscription`. A perpetual licence's whole meaning is that the right to\n * use outlives the right to updates, which needs the running build's release\n * date to enforce — a fact a certificate cannot carry, because it is issued\n * before the versions it covers exist. A build with no such date could only\n * honour a perpetual licence by honouring *every* future version forever, which\n * is not what was sold. So it is refused, and refused as `unsupported` rather\n * than `invalid`: the fix is to upgrade, and saying so is more use than calling\n * a valid certificate corrupt.\n */\nconst LICENSE_TYPES: readonly LicenseType[] = [\"subscription\"];\n\n/** Whitespace is stripped: licences get pasted, wrapped and indented. */\nexport function normalizeLicense(license: string): string {\n return license.replace(/\\s+/g, \"\");\n}\n\n/**\n * Reads a licence string without verifying it.\n *\n * Nothing here decides whether a licence is *honoured* — that needs the\n * signature — but a structurally broken string can be rejected before any\n * cryptography happens, and the account UI can display a licence's contents\n * without holding a public key.\n */\nexport function parseLicense(license: string): ParsedLicense {\n const text = normalizeLicense(license);\n if (!text) throw new LicenseFormatError(\"the licence string is empty\");\n\n const parts = text.split(\".\");\n if (parts.length !== 3) throw new LicenseFormatError(`expected three dot-separated parts, found ${parts.length}`);\n const [prefix, encodedPayload, encodedSignature] = parts as [string, string, string];\n\n if (prefix !== FORMAT_PREFIX) {\n // A licence issued in a format this SDK predates is a version problem, not\n // a corrupt string, and the fix is to upgrade rather than to re-copy it.\n const known = /^BP\\d+$/.test(prefix);\n throw new LicenseFormatError(\n known\n ? `licence format ${prefix} is newer than this version of BroadPaper, which reads ${FORMAT_PREFIX}`\n : `unrecognised licence prefix ${JSON.stringify(prefix)}`,\n known ? \"unsupported\" : \"invalid\"\n );\n }\n if (!encodedPayload || !encodedSignature) throw new LicenseFormatError(\"the licence string has an empty part\");\n\n let json: string;\n let signature: Uint8Array;\n try {\n json = bytesToUtf8(base64UrlToBytes(encodedPayload));\n signature = base64UrlToBytes(encodedSignature);\n } catch (e) {\n throw new LicenseFormatError(`the licence string is not valid base64 (${(e as Error).message})`);\n }\n if (signature.length !== 64) throw new LicenseFormatError(`an Ed25519 signature is 64 bytes, this one is ${signature.length}`);\n\n let raw: unknown;\n try {\n raw = JSON.parse(json);\n } catch {\n throw new LicenseFormatError(\"the licence payload is not valid JSON\");\n }\n\n return {\n prefix,\n signedText: `${prefix}.${encodedPayload}`,\n encodedPayload,\n signature,\n payload: readPayload(raw)\n };\n}\n\n/**\n * Validates the payload's shape.\n *\n * Unknown properties are kept, not rejected: they are inside the signature, so\n * a licence issued by a later BroadPaper carrying a field this SDK has never\n * heard of is still a licence this SDK can honour.\n */\nfunction readPayload(raw: unknown): LicensePayload {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) throw new LicenseFormatError(\"the licence payload is not an object\");\n const o = raw as Record<string, unknown>;\n\n const schemaVersion = o.schemaVersion;\n if (schemaVersion !== 1) {\n const numeric = typeof schemaVersion === \"number\" && Number.isInteger(schemaVersion) && schemaVersion > 1;\n throw new LicenseFormatError(\n numeric\n ? `licence payload schema version ${schemaVersion} is newer than this version of BroadPaper, which reads version 1`\n : \"the licence payload has no usable schemaVersion\",\n numeric ? \"unsupported\" : \"invalid\"\n );\n }\n\n const text = (key: string): string => {\n const value = o[key];\n if (typeof value !== \"string\" || value === \"\") throw new LicenseFormatError(`the licence payload is missing ${key}`);\n return value;\n };\n const seconds = (key: string): number => {\n const value = o[key];\n if (typeof value !== \"number\" || !Number.isFinite(value)) throw new LicenseFormatError(`the licence payload is missing ${key}`);\n return value;\n };\n // `null` is the honest encoding of \"no end date\" — a sentinel year 9999 would\n // be a date somebody eventually compares against. Absent is still an error;\n // only an explicit null means never.\n const secondsOrNever = (key: string): number | null => {\n const value = o[key];\n if (value === null) return null;\n return seconds(key);\n };\n\n const plan = text(\"plan\");\n if (!PLANS.includes(plan as Plan)) {\n // A plan this SDK does not know is a newer commercial model, and refusing\n // to honour a licence we cannot classify would punish the customer for\n // buying something new. It is reported as unsupported so the developer is\n // told to upgrade.\n throw new LicenseFormatError(`this version of BroadPaper does not know the plan ${JSON.stringify(plan)}`, \"unsupported\");\n }\n\n const licenseType = text(\"licenseType\");\n if (!LICENSE_TYPES.includes(licenseType as LicenseType)) {\n throw new LicenseFormatError(`this version of BroadPaper cannot honour a ${JSON.stringify(licenseType)} licence`, \"unsupported\");\n }\n\n return {\n ...o,\n schemaVersion: 1,\n keyId: text(\"keyId\"),\n licenseId: text(\"licenseId\"),\n organisationId: text(\"organisationId\"),\n organisationName: text(\"organisationName\"),\n product: text(\"product\") as typeof PRODUCT,\n plan: plan as Plan,\n licenseType: licenseType as LicenseType,\n issuedAt: seconds(\"issuedAt\"),\n validUntil: secondsOrNever(\"validUntil\"),\n updatesUntil: seconds(\"updatesUntil\")\n };\n}\n\n/**\n * Encodes a payload into the text that gets signed.\n *\n * Exported because BroadPaper's own licence issuer must produce byte-identical\n * input to what the SDK verifies, and the only way to guarantee that is for\n * both to call this function.\n */\nexport function encodeSigningInput(payload: LicensePayload): { signedText: string; encodedPayload: string } {\n const encodedPayload = bytesToBase64Url(utf8ToBytes(JSON.stringify(payload)));\n return { encodedPayload, signedText: `${FORMAT_PREFIX}.${encodedPayload}` };\n}\n\n/** Assembles the final licence string from a payload and its signature. */\nexport function formatLicense(payload: LicensePayload, signature: Uint8Array): string {\n if (signature.length !== 64) throw new LicenseFormatError(`an Ed25519 signature is 64 bytes, this one is ${signature.length}`);\n const { encodedPayload } = encodeSigningInput(payload);\n return `${FORMAT_PREFIX}.${encodedPayload}.${bytesToBase64Url(signature)}`;\n}\n","/**\n * The public keys this build of the SDK will accept licence signatures from.\n *\n * **This file contains public keys only.** BroadPaper's private signing key\n * lives in the licence issuer's secret store and is never in this repository,\n * never in the npm package and never in a browser bundle. Publishing the\n * public half is the point: it is what lets a licence be checked with no\n * network call.\n *\n * Add a production key with:\n *\n * node scripts/licence-keys.mjs --production --key-id bp-1\n *\n * which prints the private key once, for the operator to paste into the\n * issuer's configuration, and writes the public half into this array.\n *\n * The list is an allowlist, so it is also the rotation mechanism: issue with a\n * new `keyId`, ship an SDK release trusting both, and drop the old entry once\n * every licence signed with it has expired. Licences carry their `keyId` inside\n * the signed payload, so an attacker cannot point one at a different key.\n *\n * It starts empty on purpose. A key here is trusted by every copy of the SDK in\n * the world, including production installs, which is exactly why a development\n * key must never be added to it — development keys are supplied at runtime\n * instead, through `configure({ trustedKeys })` or `BROADPAPER_TRUSTED_KEYS`,\n * so a licence signed in development cannot be honoured by a production build.\n */\nimport type { TrustedKey } from \"./types.js\";\n\nexport const TRUSTED_KEYS: readonly TrustedKey[] = [\n { keyId: \"bp-2026\", publicKey: \"woKmWyIazae7SpGhi4dwA7zNK1lWdboq88Rl5xscTs4\", note: \"production, generated 2026-09-08\" }\n];\n","/**\n * The licence certificate and the states it can be in.\n *\n * A BroadPaper licence is a signed certificate, not a secret. It says who\n * bought what and until when, BroadPaper signs it, and the SDK checks that\n * signature with a public key compiled into the package. Nothing here talks to\n * a network, reads a machine identifier or writes a file: a licence that is\n * valid on a laptop with no route to the internet is valid, full stop.\n */\n\n/**\n * Which licence was bought. Not a feature gate — both plans unlock exactly the\n * same software. The difference is who the resulting application may be put in\n * front of, which is a contractual matter and is presented as one in the\n * account area and the licence agreement.\n *\n * These are the `id`s in `shared/plans.json`, deliberately: that file is the\n * one machine-readable statement of the commercial model, and a certificate\n * that quotes a different vocabulary from the thing that priced it is a\n * certificate somebody eventually has to write a mapping table for.\n */\nexport type Plan = \"internal\" | \"saas\";\n\n/**\n * How the licence is held.\n *\n * `subscription` is a term: the right to use the software ends with the term,\n * and so does the right to updates. It is the only thing sold today.\n *\n * `perpetual` is reserved. It would mean the right to use never ends, while\n * the right to updates still does — `validUntil` null, `updatesUntil` set. The\n * field exists now so that offering one later is a change to what the issuer\n * writes rather than a change to the certificate format, which would mean\n * reissuing every licence in the field. **This SDK does not honour one**: see\n * `readPayload` in `format.ts` for why refusing is the only safe answer.\n */\nexport type LicenseType = \"subscription\" | \"perpetual\";\n\n/** The product a licence is for. Present so one signing key can serve more later. */\nexport const PRODUCT = \"broadpaper-sdk\";\n\n/**\n * The signed payload.\n *\n * `organisationId` is the durable identifier of the buying organisation — a\n * Clerk organisation id — rather than an email address, because people change\n * addresses and organisations outlive their founders. It is *not* the Creem\n * customer id, which identifies the same buyer to the payment processor and is\n * a different string; the API keeps both, and naming this one for the\n * organisation is what stops the two being confused at a glance.\n *\n * Times are epoch **seconds**, not milliseconds and not ISO strings. The\n * certificate is written by `Utf8JsonWriter` in C# and read by `JSON.parse` in\n * TypeScript, and an integer cannot disagree about a timezone suffix or a\n * fractional second the way two date serialisers can — which matters more here\n * than anywhere, because the two halves only find out they disagree when a\n * customer's licence is rejected in the field.\n *\n * Field order is load-bearing on the issuing side only: `encodeSigningInput`\n * serialises this object, and `LicenceSigner.Payload` writes the same keys in\n * the same order so a certificate is byte-identical whichever issued it.\n * Verification never re-serialises — it checks the signature over the text as\n * received — so an unknown field from a later BroadPaper is carried through\n * rather than rejected.\n */\nexport interface LicensePayload {\n /**\n * Payload schema version. Bumped only when the meaning of a field changes,\n * never to add one: `readPayload` keeps fields it does not know about, so a\n * later BroadPaper can extend the certificate without older SDKs refusing it.\n */\n schemaVersion: 1;\n /** Which signing key signed this. Lets keys be rotated without reissuing everything. */\n keyId: string;\n /**\n * BroadPaper's own identifier for *this certificate*, `lic_…`.\n *\n * One per issuance, so a renewal or an upgrade carries a new one and the\n * document it produced can be dated from it. The buyer is identified by\n * `organisationId`, which does not change.\n */\n licenseId: string;\n /** The licensed organisation's durable id. A Clerk organisation id. */\n organisationId: string;\n /** The organisation's name, for display in warnings and PDF metadata. */\n organisationName: string;\n product: typeof PRODUCT;\n plan: Plan;\n licenseType: LicenseType;\n /** Epoch seconds. */\n issuedAt: number;\n /**\n * When the right to *use* the software ends, in epoch seconds.\n *\n * Null means never, which only a perpetual licence says. A subscription\n * always carries a date.\n */\n validUntil: number | null;\n /**\n * When the right to *updates* ends, in epoch seconds.\n *\n * For a subscription this equals `validUntil` and nothing reads it: the term\n * ends, and both rights end with it. It is separate from `validUntil` because\n * for a perpetual licence the two genuinely differ, and that is the whole\n * reason the field is here before anything consults it.\n */\n updatesUntil: number;\n}\n\n/**\n * Why a licence is not being honoured, or how it is being honoured.\n *\n * These are deliberately coarse. A developer needs to know whether their PDFs\n * will carry the evaluation mark and, if so, what to do about it; a taxonomy of\n * eleven parse failures helps nobody.\n */\nexport type LicenseState =\n /** No licence was configured. The SDK works; PDFs carry the evaluation mark. */\n | \"unlicensed\"\n /** A licence was configured but could not be read, or its signature is wrong. */\n | \"invalid\"\n /** Signed by a key this build of the SDK does not trust. */\n | \"untrusted\"\n /** For a different product, or a certificate this SDK is too old to honour. */\n | \"unsupported\"\n /** Valid, signed, and inside its term. */\n | \"active\"\n /** Past `validUntil` but inside the grace period: honoured, and warned about. */\n | \"grace\"\n /** Past `validUntil` and past the grace period. */\n | \"expired\";\n\n/** The outcome of checking a licence. Everything the SDK needs to decide. */\nexport interface LicenseStatus {\n state: LicenseState;\n /** True when PDFs render clean. `active` and `grace` only. */\n licensed: boolean;\n /** The payload, when one could be read and verified. */\n payload: LicensePayload | null;\n /** Which licence was bought, when it is being honoured. */\n plan: Plan | null;\n /** When the right to use ends, when known. Null for a licence with no end. */\n validUntil: Date | null;\n /** Days until `validUntil`; negative once expired. Null when there is no end date. */\n daysRemaining: number | null;\n /**\n * A developer-facing sentence. Always present for a state that is not\n * `active`, so callers can log it without composing their own prose.\n */\n message: string | null;\n}\n\n/** A public key this build of the SDK will accept signatures from. */\nexport interface TrustedKey {\n /** Matches `keyId` in the payload. */\n keyId: string;\n /** The raw 32-byte Ed25519 public key, base64url or base64. */\n publicKey: string;\n /** Free text for the key's provenance. Never displayed to end users. */\n note?: string;\n}\n","/**\n * Configuring BroadPaper once, for the life of the process.\n *\n * import { BroadPaper } from \"@broadpaper/forme\";\n * BroadPaper.configure({ license: process.env.BROADPAPER_LICENSE });\n *\n * The licence is not a credential, so this is not a secrets API: it is the same\n * shape of thing as setting a locale. A browser bundle that carries the licence\n * string in plain text is fine — the string is a signed certificate, and\n * possessing it grants nothing beyond what its payload already says.\n *\n * Configuration is deliberately process-global rather than an argument to every\n * render. Threading a licence through `renderPdf`, the render service, the\n * batch endpoint, the .NET client and every future export path is exactly the\n * kind of plumbing that ends with one path quietly forgetting it, and a\n * customer who has paid discovering a watermark in production.\n *\n * The store hangs off a `Symbol.for` key rather than a module-level variable so\n * that two copies of this package in one dependency tree — the usual result of\n * a mixed-version install — still agree about the licence.\n */\nimport { unlicensed, verifyLicense, type VerifyOptions } from \"./verify.js\";\nimport type { LicenseStatus, TrustedKey } from \"./types.js\";\n\nexport interface BroadPaperConfig {\n /**\n * The licence string, `BP1.…`. Read from `BROADPAPER_LICENSE` when this is\n * not set and the runtime has environment variables.\n */\n license?: string | null;\n /**\n * Public keys to trust in addition to the ones built into the SDK. This is\n * how a development licence is honoured on a developer's machine without a\n * development key ever being trusted by a production build.\n */\n trustedKeys?: readonly TrustedKey[];\n /** Overrides the 30-day post-expiry grace period. Mostly for tests. */\n gracePeriodDays?: number;\n /** Where licence warnings go. Defaults to `console.warn`. */\n logger?: ((message: string) => void) | null;\n}\n\ninterface Store {\n config: BroadPaperConfig;\n /** Memoised by the licence string that produced it. */\n cache: { key: string; status: Promise<LicenseStatus> } | null;\n /** Messages already logged, so a hundred renders do not print a hundred lines. */\n logged: Set<string>;\n}\n\nconst STORE_KEY = Symbol.for(\"broadpaper.license.store\");\n\nfunction store(): Store {\n const g = globalThis as Record<symbol, unknown>;\n let s = g[STORE_KEY] as Store | undefined;\n if (!s) {\n s = { config: {}, cache: null, logged: new Set() };\n g[STORE_KEY] = s;\n }\n return s;\n}\n\n/**\n * Sets the licence and any other SDK-wide options. Call it once, at startup.\n *\n * Successive calls merge, so a host can set the licence in one place and the\n * grace period in another; passing `license: null` clears it.\n */\nexport function configure(config: BroadPaperConfig): void {\n const s = store();\n s.config = { ...s.config, ...config };\n s.cache = null;\n s.logged.clear();\n}\n\n/** What {@link configure} has been given, plus whatever the environment supplies. */\nexport function getConfig(): BroadPaperConfig {\n return { ...store().config };\n}\n\n/** Forgets the configuration entirely. Exists for tests. */\nexport function resetConfig(): void {\n const g = globalThis as Record<symbol, unknown>;\n delete g[STORE_KEY];\n}\n\n/**\n * `BroadPaper.configure({ … })`, for hosts that prefer a namespace to a bare\n * function. Both are the same store.\n */\nexport const BroadPaper = { configure, getConfig };\n\nfunction env(name: string): string | undefined {\n // Node, Deno and bundlers that inline `process.env` all satisfy this; a\n // browser without a shim simply has no environment, which is not an error.\n const p = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\n const value = p?.env?.[name];\n return value && value.trim() ? value : undefined;\n}\n\n/**\n * Keys trusted in addition to the built-in ones, from configuration and from\n * `BROADPAPER_TRUSTED_KEYS` — a JSON array of `{ keyId, publicKey }`.\n *\n * A syntax error there is reported rather than swallowed: someone setting that\n * variable is doing so on purpose, and silently ignoring it would present as\n * \"my development licence is not being honoured\" with nothing to go on.\n */\nfunction trustedKeysFromEnv(): readonly TrustedKey[] {\n const raw = env(\"BROADPAPER_TRUSTED_KEYS\");\n if (!raw) return [];\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) throw new Error(\"expected a JSON array\");\n return parsed.map((entry, i) => {\n const k = entry as Record<string, unknown>;\n if (typeof k?.keyId !== \"string\" || typeof k?.publicKey !== \"string\") throw new Error(`entry ${i} needs keyId and publicKey`);\n return { keyId: k.keyId, publicKey: k.publicKey };\n });\n } catch (e) {\n warn(`BROADPAPER_TRUSTED_KEYS could not be read (${(e as Error).message}); it is being ignored.`);\n return [];\n }\n}\n\n/** Logs a message once, whatever it takes to reach the developer. */\nexport function warn(message: string): void {\n const s = store();\n if (s.logged.has(message)) return;\n s.logged.add(message);\n const logger = s.config.logger;\n if (logger === null) return;\n const line = `[BroadPaper] ${message}`;\n if (logger) logger(line);\n else console.warn(line);\n}\n\nexport interface ResolveOptions extends VerifyOptions {\n /** A licence for this call only, overriding the configured one. */\n license?: string | null;\n}\n\n/**\n * The licence status for the current configuration.\n *\n * Memoised on the licence string, because every PDF asks and a signature check\n * per page of a five-hundred-page batch is waste. `configure` clears it, and a\n * per-call `license` bypasses it.\n *\n * Whatever the answer, any developer-facing message is logged once. That is the\n * only thing the SDK does that a caller did not ask for, and it is the\n * difference between a developer seeing \"your licence expired\" and a developer\n * seeing an unexplained watermark.\n */\nexport async function resolveLicenseStatus(options: ResolveOptions = {}): Promise<LicenseStatus> {\n const s = store();\n const config = s.config;\n const license = options.license !== undefined ? options.license : config.license ?? env(\"BROADPAPER_LICENSE\") ?? null;\n const trustedKeys = [...(options.trustedKeys ?? []), ...(config.trustedKeys ?? []), ...trustedKeysFromEnv()];\n const gracePeriodDays = options.gracePeriodDays ?? config.gracePeriodDays;\n\n if (!license) {\n const status = unlicensed();\n if (status.message) warn(status.message);\n return status;\n }\n\n const verify = (): Promise<LicenseStatus> =>\n verifyLicense(license, { ...(options.now ? { now: options.now } : {}), trustedKeys, ...(gracePeriodDays !== undefined ? { gracePeriodDays } : {}) }).then((status) => {\n if (status.message) warn(status.message);\n return status;\n });\n\n // A fixed clock is a test or a deterministic render; caching across different\n // clocks would make the grace-period boundary depend on call order.\n if (options.now || options.license !== undefined) return verify();\n\n const key = `${license}|${trustedKeys.map((k) => k.keyId).join(\",\")}|${gracePeriodDays ?? \"\"}`;\n if (s.cache?.key !== key) s.cache = { key, status: verify() };\n return s.cache.status;\n}\n","/**\n * The evaluation mark.\n *\n * This is the only thing an unlicensed BroadPaper does differently. Every\n * block, both PDF backends, the designer, data binding, pagination and the\n * render service behave identically with and without a licence; what changes is\n * that the file says, on every page, that it was produced with an evaluation\n * copy.\n *\n * It is drawn to be unmistakable in a document review and harmless to a\n * developer evaluating the product: a light diagonal line across the page,\n * behind nothing and over everything, at an opacity that leaves the report\n * entirely readable. Making the output illegible would only push people towards\n * evaluating something else.\n *\n * Each backend draws the spec with its own primitives — the Forme engine has a\n * page-native watermark node, Chromium gets a printed overlay — because a\n * single drawing implementation would mean one of them post-processing a file\n * the other had already finished, and a stamping pass that can be skipped is a\n * stamping pass that eventually is.\n */\nimport { resolveLicenseStatus, type ResolveOptions } from \"./config.js\";\nimport type { LicenseStatus } from \"./types.js\";\n\nexport interface WatermarkSpec {\n text: string;\n /** Points. The engines size the mark from this, not from the page. */\n fontSize: number;\n /** Degrees, anticlockwise from horizontal. */\n angle: number;\n /** Hex, without alpha. */\n color: string;\n /** 0–1. */\n opacity: number;\n /**\n * Where on the page it goes.\n *\n * `center` is the evaluation mark's diagonal stamp: sized from `fontSize`,\n * rotated by `angle`, across the middle of every page. It is the default and\n * the only thing `watermarkFor` ever asks for.\n *\n * `bottom` is a credit line along the foot of the page, in the bottom margin —\n * the shape a host's own commercial model usually wants. BroadPaper Cloud's\n * free plan started as a `center` mark at eight points, which is a small grey\n * sentence floating in the middle of an otherwise finished invoice: it reads\n * as a mistake rather than as a credit, and the whole point of that mark is\n * that a free user's document stays sendable.\n *\n * It is drawn in the margin rather than in the footer band deliberately, so it\n * costs no layout: a mark that changed the content height would make the\n * finished file paginate differently from the canvas the user approved.\n */\n placement?: \"center\" | \"bottom\";\n /**\n * A small mark drawn before the text, as an `image/svg+xml` data URI. `bottom`\n * only.\n *\n * Vector rather than a bitmap because it is drawn at about nine points and a\n * raster at that size is a smudge. Both backends parse the URI themselves —\n * the Forme engine's image decoder cannot read an SVG, so it is re-emitted as\n * the drawing it is.\n */\n mark?: string;\n /**\n * Where the text links to, making it a link in the file. `bottom` only, and\n * the Forme backend only — the Chromium backend stamps text into a finished\n * file and draws it unlinked.\n *\n * For a host's own credit line, whose job is to be followed: a free document a\n * business sends a client is the most-read advert the host will ever have, and\n * an address nobody can click is one hardly anybody types. The evaluation mark\n * never has one — `watermarkFor` does not set it — because that mark is there to\n * be seen, not visited.\n */\n href?: string;\n}\n\n/**\n * The mark itself.\n *\n * No dashes or typographic punctuation: the standard PDF faces carry no em\n * dash, and a theme that names a font the engine does not have falls back to\n * them, which turned \"BroadPaper — Evaluation Copy\" into \"BroadPaper\n * Evaluation Copy\" in half the documents and a question mark in the rest.\n */\nexport const EVALUATION_WATERMARK: WatermarkSpec = {\n text: \"BroadPaper Evaluation Copy\",\n fontSize: 44,\n angle: -35,\n color: \"#c8102e\",\n opacity: 0.12\n};\n\n/**\n * The mark a render should carry, or null when the licence says none.\n *\n * Backends call this rather than reading the licence status themselves, so\n * \"licensed\" and \"no watermark\" cannot drift apart.\n */\nexport async function resolveWatermark(options: ResolveOptions = {}): Promise<WatermarkSpec | null> {\n const status = await resolveLicenseStatus(options);\n return watermarkFor(status);\n}\n\n/** The pure half of {@link resolveWatermark}, for callers that already have a status. */\nexport function watermarkFor(status: LicenseStatus): WatermarkSpec | null {\n return status.licensed ? null : EVALUATION_WATERMARK;\n}\n\n/**\n * CSS that paints the mark on every `.bp-page` of a rendered document.\n *\n * Used by the Chromium backend and by the designer's print path — the two\n * places a PDF is produced by a browser rather than by the engine. `position:\n * fixed` would put it on the viewport rather than on each page, so it is an\n * absolutely positioned pseudo-element on the page box, which prints once per\n * sheet.\n *\n * `print-color-adjust` matters: without it Chromium drops the mark from the\n * printed file exactly as it drops any other background colour.\n */\nexport function watermarkCss(spec: WatermarkSpec = EVALUATION_WATERMARK): string {\n const text = spec.text.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n return [\n \".bp-page{position:relative;}\",\n \".bp-page::after{\",\n `content:\"${text}\";`,\n \"position:absolute;inset:0;\",\n \"display:flex;align-items:center;justify-content:center;\",\n \"pointer-events:none;z-index:2147483647;\",\n `transform:rotate(${spec.angle}deg);`,\n \"font-family:Helvetica,Arial,sans-serif;font-weight:700;letter-spacing:0.04em;white-space:nowrap;\",\n `font-size:${spec.fontSize}pt;color:${spec.color};opacity:${spec.opacity};`,\n \"-webkit-print-color-adjust:exact;print-color-adjust:exact;\",\n \"}\"\n ].join(\"\");\n}\n\n/**\n * The PDF Producer string for a render.\n *\n * Licensed output records the licence id. It is not a secret — it identifies a\n * customer's licence, not their account — and having it in the file means a\n * document that turns up somewhere it should not can be traced back to the\n * licence that produced it without any of the tracking a phone-home would need.\n */\nexport function producerString(status: LicenseStatus): string {\n if (!status.licensed || !status.payload) return \"BroadPaper (evaluation)\";\n return `BroadPaper (${status.payload.licenseId})`;\n}\n"],"mappings":"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","names":["i"]}
|
|
1
|
+
{"version":3,"sources":["../src/verify.ts","../src/encoding.ts","../src/format.ts","../src/trusted-keys.ts","../src/types.ts","../src/config.ts","../src/watermark.ts"],"sourcesContent":["/**\n * Offline licence verification.\n *\n * Everything here is local: a signature check against a compiled-in public key\n * and a comparison against the clock. There is no activation call, no licence\n * server and nothing to be offline from. That is a deliberate product decision\n * as much as a technical one — a report renderer that stops working because a\n * key server is down is a report renderer nobody puts in a payroll run.\n *\n * The failure modes are all *soft*. An unreadable licence never throws out of a\n * render; it produces a status whose `message` says what is wrong, and the PDF\n * comes out carrying the evaluation mark.\n */\nimport { verifyAsync } from \"@noble/ed25519\";\nimport { base64UrlToBytes, utf8ToBytes } from \"./encoding.js\";\nimport { LicenseFormatError, parseLicense } from \"./format.js\";\nimport { TRUSTED_KEYS } from \"./trusted-keys.js\";\nimport { EVALUATION_PLAN, PRODUCT, type LicensePayload, type LicenseStatus, type TrustedKey } from \"./types.js\";\n\n/**\n * How long an expired licence keeps working.\n *\n * A renewal that is a week late is an accounts payable problem, not a reason to\n * stamp a customer's month-end reports. Thirty days is long enough to cover a\n * card expiring over a holiday and short enough that it is not a second licence\n * term.\n */\nexport const DEFAULT_GRACE_PERIOD_DAYS = 30;\n\n/** Where a developer gets a free evaluation key. Printed, never fetched. */\nexport const EVALUATION_KEY_URL = \"https://www.broadpaper.com/eval\";\n\nconst DAY_MS = 86_400_000;\n\nexport interface VerifyOptions {\n /** Fixed clock. Tests and deterministic renders pass one. */\n now?: Date;\n /** Extra keys to trust, on top of the ones built into this SDK. */\n trustedKeys?: readonly TrustedKey[];\n /**\n * Overrides {@link DEFAULT_GRACE_PERIOD_DAYS}. Never applies to an evaluation\n * licence, which has no grace period whatever a host configures.\n */\n gracePeriodDays?: number;\n}\n\n/** The status of a caller that supplied no licence at all. */\nexport function unlicensed(): LicenseStatus {\n return {\n state: \"unlicensed\",\n licensed: false,\n payload: null,\n plan: null,\n validUntil: null,\n daysRemaining: null,\n message: `BroadPaper is running in evaluation mode. Get a free evaluation key: ${EVALUATION_KEY_URL}`\n };\n}\n\nfunction failure(state: LicenseStatus[\"state\"], message: string, payload: LicensePayload | null = null): LicenseStatus {\n return {\n state,\n licensed: false,\n payload,\n plan: null,\n validUntil: payload?.validUntil != null ? new Date(payload.validUntil * 1000) : null,\n daysRemaining: null,\n message\n };\n}\n\n/**\n * Checks a licence string and reports what it entitles the caller to.\n *\n * Never throws for a bad licence: a malformed string, a forged signature and an\n * expired term are all ordinary answers, and a render is not the place to\n * discover that a config file has a typo in it.\n */\nexport async function verifyLicense(license: string | null | undefined, options: VerifyOptions = {}): Promise<LicenseStatus> {\n if (!license || !license.trim()) return unlicensed();\n\n let parsed;\n try {\n parsed = parseLicense(license);\n } catch (e) {\n if (e instanceof LicenseFormatError) {\n return failure(\n e.kind,\n e.kind === \"unsupported\"\n ? `This BroadPaper licence needs a newer version of the SDK (${e.message}). Update @broadpaper/* or the PDF will carry the evaluation watermark.`\n : `The BroadPaper licence could not be read (${e.message}), so PDFs carry the evaluation watermark.`\n );\n }\n /* c8 ignore next -- parseLicense only throws LicenseFormatError */\n throw e;\n }\n\n const { payload } = parsed;\n if (payload.product !== PRODUCT) {\n return failure(\n \"invalid\",\n `This licence is for ${JSON.stringify(payload.product)}, not the BroadPaper SDK, so PDFs carry the evaluation watermark.`,\n payload\n );\n }\n\n const key = findKey(payload.keyId, options.trustedKeys);\n if (!key) {\n return failure(\n \"untrusted\",\n `This BroadPaper licence was signed with key ${JSON.stringify(payload.keyId)}, which this build does not trust. ` +\n \"A licence issued for development is not honoured by a production build; check BROADPAPER_LICENSE points at the right one.\",\n payload\n );\n }\n\n let signatureOk = false;\n try {\n signatureOk = await verifyAsync(parsed.signature, utf8ToBytes(parsed.signedText), base64UrlToBytes(key.publicKey));\n } catch {\n // A malformed public key or a point that is not on the curve. Either way\n // this licence is not going to verify, and a render must not fail over it.\n signatureOk = false;\n }\n if (!signatureOk) {\n return failure(\"invalid\", \"This BroadPaper licence failed its signature check — it has been altered or truncated. PDFs carry the evaluation watermark.\", payload);\n }\n\n const now = options.now ?? new Date();\n\n // A licence with no end date never expires, so there is no clock to consult.\n // Nothing sold today issues one — `readPayload` refuses the only licence type\n // that would — but the field is nullable, and a null that fell through to the\n // arithmetic below would come out as 1970 and watermark a valid licence.\n if (payload.validUntil === null) {\n return { state: \"active\", licensed: true, payload, plan: payload.plan, validUntil: null, daysRemaining: null, message: null };\n }\n\n const validUntil = new Date(payload.validUntil * 1000);\n const evaluation = payload.plan === EVALUATION_PLAN;\n // An evaluation licence ends on its date. Paid licences get thirty days\n // because a late renewal is an accounts-payable problem; a free key has no\n // renewal to be late with, and the way to get more time is to ask for it.\n const graceDays = evaluation ? 0 : options.gracePeriodDays ?? DEFAULT_GRACE_PERIOD_DAYS;\n const day = validUntil.toISOString().slice(0, 10);\n const msRemaining = validUntil.getTime() - now.getTime();\n const daysRemaining = Math.floor(msRemaining / DAY_MS);\n\n if (msRemaining > 0) {\n return {\n state: \"active\",\n licensed: true,\n payload,\n plan: payload.plan,\n validUntil,\n daysRemaining,\n message: null,\n notice: evaluation\n ? `BroadPaper is running on an evaluation licence for ${payload.organisationName}, which ends on ${day}. ` +\n \"It is for evaluation and development, not production use.\"\n : null\n };\n }\n\n if (evaluation) {\n return failure(\n \"expired\",\n `The BroadPaper evaluation licence for ${payload.organisationName} ended on ${day}, so PDFs carry the evaluation watermark again. ` +\n \"Need longer? Reply to your licence email or write to hello@broadpaper.com.\",\n payload\n );\n }\n\n if (msRemaining > -graceDays * DAY_MS) {\n const daysOver = Math.max(1, Math.ceil(-msRemaining / DAY_MS));\n return {\n state: \"grace\",\n licensed: true,\n payload,\n plan: payload.plan,\n validUntil,\n daysRemaining,\n message:\n `The BroadPaper licence for ${payload.organisationName} expired ${daysOver} day${daysOver === 1 ? \"\" : \"s\"} ago and is inside its ${graceDays}-day grace period. ` +\n \"Renew it and copy the new licence from your account, or PDFs will start carrying the evaluation watermark.\"\n };\n }\n\n return failure(\n \"expired\",\n `The BroadPaper licence for ${payload.organisationName} expired on ${day} and its ${graceDays}-day grace period has passed, ` +\n \"so PDFs carry the evaluation watermark. Renewing issues a new licence in your BroadPaper account.\",\n payload\n );\n}\n\nfunction findKey(keyId: string, extra: readonly TrustedKey[] | undefined): TrustedKey | undefined {\n // Runtime keys are searched first so a development key can shadow a built-in\n // one of the same name without a rebuild.\n return extra?.find((k) => k.keyId === keyId) ?? TRUSTED_KEYS.find((k) => k.keyId === keyId);\n}\n","/**\n * base64url and UTF-8, without depending on Node or on the DOM.\n *\n * The SDK runs in Node, in edge runtimes, in a worker and in a browser, and the\n * licence string has to decode identically in all four. `atob`/`btoa` exist\n * everywhere modern but are byte-oriented and mangle anything above U+00FF, so\n * text goes through `TextEncoder`/`TextDecoder` first and only bytes are\n * base64'd. The tables below are the whole implementation, which is smaller\n * than the runtime detection needed to use `Buffer` where it happens to exist.\n */\n\nconst ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\nconst LOOKUP = /* @__PURE__ */ (() => {\n const t = new Int8Array(128).fill(-1);\n for (let i = 0; i < ALPHABET.length; i++) t[ALPHABET.charCodeAt(i)] = i;\n // Accept standard base64 as well, so a licence that survived a round trip\n // through a system that rewrote the alphabet still reads.\n t[\"+\".charCodeAt(0)] = 62;\n t[\"/\".charCodeAt(0)] = 63;\n return t;\n})();\n\nexport function bytesToBase64Url(bytes: Uint8Array): string {\n let out = \"\";\n let i = 0;\n for (; i + 2 < bytes.length; i += 3) {\n const n = (bytes[i]! << 16) | (bytes[i + 1]! << 8) | bytes[i + 2]!;\n out += ALPHABET[(n >>> 18) & 63]! + ALPHABET[(n >>> 12) & 63]! + ALPHABET[(n >>> 6) & 63]! + ALPHABET[n & 63]!;\n }\n const rest = bytes.length - i;\n if (rest === 1) {\n const n = bytes[i]! << 16;\n out += ALPHABET[(n >>> 18) & 63]! + ALPHABET[(n >>> 12) & 63]!;\n } else if (rest === 2) {\n const n = (bytes[i]! << 16) | (bytes[i + 1]! << 8);\n out += ALPHABET[(n >>> 18) & 63]! + ALPHABET[(n >>> 12) & 63]! + ALPHABET[(n >>> 6) & 63]!;\n }\n return out;\n}\n\n/** Throws on anything that is not base64/base64url. Padding is optional. */\nexport function base64UrlToBytes(text: string): Uint8Array {\n let end = text.length;\n while (end > 0 && text[end - 1] === \"=\") end--;\n const chars: number[] = [];\n for (let i = 0; i < end; i++) {\n const code = text.charCodeAt(i);\n const value = code < 128 ? LOOKUP[code]! : -1;\n if (value < 0) throw new Error(`not base64: ${JSON.stringify(text[i])} at ${i}`);\n chars.push(value);\n }\n // 4 base64 characters carry 3 bytes; a group of 1 is never valid.\n if (chars.length % 4 === 1) throw new Error(\"not base64: truncated\");\n const out = new Uint8Array(Math.floor((chars.length * 3) / 4));\n let o = 0;\n let i = 0;\n for (; i + 3 < chars.length; i += 4) {\n const n = (chars[i]! << 18) | (chars[i + 1]! << 12) | (chars[i + 2]! << 6) | chars[i + 3]!;\n out[o++] = (n >>> 16) & 255;\n out[o++] = (n >>> 8) & 255;\n out[o++] = n & 255;\n }\n const rest = chars.length - i;\n if (rest === 2) {\n out[o++] = ((chars[i]! << 18) | (chars[i + 1]! << 12)) >>> 16;\n } else if (rest === 3) {\n const n = (chars[i]! << 18) | (chars[i + 1]! << 12) | (chars[i + 2]! << 6);\n out[o++] = (n >>> 16) & 255;\n out[o++] = (n >>> 8) & 255;\n }\n return out;\n}\n\nconst encoder = /* @__PURE__ */ new TextEncoder();\nconst decoder = /* @__PURE__ */ new TextDecoder(\"utf-8\", { fatal: true });\n\nexport const utf8ToBytes = (text: string): Uint8Array => encoder.encode(text);\nexport const bytesToUtf8 = (bytes: Uint8Array): string => decoder.decode(bytes);\n","/**\n * The licence string.\n *\n * BP1.<base64url payload>.<base64url signature>\n *\n * Three parts, dot-separated, safe in an environment variable, a YAML file, a\n * URL and a shell argument without quoting. The leading `BP1` is the *string\n * format* version and `payload.version` is the *payload schema* version; they\n * move independently, and both are checked, so a licence issued for a later\n * format is reported as unsupported rather than as corrupt.\n *\n * What is signed is the bytes of `BP1.<payload>` — prefix included. Signing the\n * encoded text rather than the decoded object means verification never depends\n * on two JSON serialisers agreeing about key order or number formatting, and\n * covering the prefix stops a payload being lifted into a future format that\n * might read a field differently.\n */\nimport { base64UrlToBytes, bytesToBase64Url, bytesToUtf8, utf8ToBytes } from \"./encoding.js\";\nimport { PRODUCT, type LicensePayload, type LicenseType, type Plan } from \"./types.js\";\n\n/** The only string format this SDK can read. */\nexport const FORMAT_PREFIX = \"BP1\";\n\n/** A licence string split into its parts, before any signature check. */\nexport interface ParsedLicense {\n prefix: string;\n /** The exact text that was signed. */\n signedText: string;\n encodedPayload: string;\n signature: Uint8Array;\n payload: LicensePayload;\n}\n\nexport class LicenseFormatError extends Error {\n constructor(\n message: string,\n /** `unsupported` when the licence is well-formed but newer than this SDK. */\n readonly kind: \"invalid\" | \"unsupported\" = \"invalid\"\n ) {\n super(message);\n this.name = \"LicenseFormatError\";\n }\n}\n\nconst PLANS: readonly Plan[] = [\"internal\", \"saas\", \"evaluation\"];\n\n/**\n * The licence types this SDK can actually honour.\n *\n * Only `subscription`. A perpetual licence's whole meaning is that the right to\n * use outlives the right to updates, which needs the running build's release\n * date to enforce — a fact a certificate cannot carry, because it is issued\n * before the versions it covers exist. A build with no such date could only\n * honour a perpetual licence by honouring *every* future version forever, which\n * is not what was sold. So it is refused, and refused as `unsupported` rather\n * than `invalid`: the fix is to upgrade, and saying so is more use than calling\n * a valid certificate corrupt.\n */\nconst LICENSE_TYPES: readonly LicenseType[] = [\"subscription\"];\n\n/** Whitespace is stripped: licences get pasted, wrapped and indented. */\nexport function normalizeLicense(license: string): string {\n return license.replace(/\\s+/g, \"\");\n}\n\n/**\n * Reads a licence string without verifying it.\n *\n * Nothing here decides whether a licence is *honoured* — that needs the\n * signature — but a structurally broken string can be rejected before any\n * cryptography happens, and the account UI can display a licence's contents\n * without holding a public key.\n */\nexport function parseLicense(license: string): ParsedLicense {\n const text = normalizeLicense(license);\n if (!text) throw new LicenseFormatError(\"the licence string is empty\");\n\n const parts = text.split(\".\");\n if (parts.length !== 3) throw new LicenseFormatError(`expected three dot-separated parts, found ${parts.length}`);\n const [prefix, encodedPayload, encodedSignature] = parts as [string, string, string];\n\n if (prefix !== FORMAT_PREFIX) {\n // A licence issued in a format this SDK predates is a version problem, not\n // a corrupt string, and the fix is to upgrade rather than to re-copy it.\n const known = /^BP\\d+$/.test(prefix);\n throw new LicenseFormatError(\n known\n ? `licence format ${prefix} is newer than this version of BroadPaper, which reads ${FORMAT_PREFIX}`\n : `unrecognised licence prefix ${JSON.stringify(prefix)}`,\n known ? \"unsupported\" : \"invalid\"\n );\n }\n if (!encodedPayload || !encodedSignature) throw new LicenseFormatError(\"the licence string has an empty part\");\n\n let json: string;\n let signature: Uint8Array;\n try {\n json = bytesToUtf8(base64UrlToBytes(encodedPayload));\n signature = base64UrlToBytes(encodedSignature);\n } catch (e) {\n throw new LicenseFormatError(`the licence string is not valid base64 (${(e as Error).message})`);\n }\n if (signature.length !== 64) throw new LicenseFormatError(`an Ed25519 signature is 64 bytes, this one is ${signature.length}`);\n\n let raw: unknown;\n try {\n raw = JSON.parse(json);\n } catch {\n throw new LicenseFormatError(\"the licence payload is not valid JSON\");\n }\n\n return {\n prefix,\n signedText: `${prefix}.${encodedPayload}`,\n encodedPayload,\n signature,\n payload: readPayload(raw)\n };\n}\n\n/**\n * Validates the payload's shape.\n *\n * Unknown properties are kept, not rejected: they are inside the signature, so\n * a licence issued by a later BroadPaper carrying a field this SDK has never\n * heard of is still a licence this SDK can honour.\n */\nfunction readPayload(raw: unknown): LicensePayload {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) throw new LicenseFormatError(\"the licence payload is not an object\");\n const o = raw as Record<string, unknown>;\n\n const schemaVersion = o.schemaVersion;\n if (schemaVersion !== 1) {\n const numeric = typeof schemaVersion === \"number\" && Number.isInteger(schemaVersion) && schemaVersion > 1;\n throw new LicenseFormatError(\n numeric\n ? `licence payload schema version ${schemaVersion} is newer than this version of BroadPaper, which reads version 1`\n : \"the licence payload has no usable schemaVersion\",\n numeric ? \"unsupported\" : \"invalid\"\n );\n }\n\n const text = (key: string): string => {\n const value = o[key];\n if (typeof value !== \"string\" || value === \"\") throw new LicenseFormatError(`the licence payload is missing ${key}`);\n return value;\n };\n const seconds = (key: string): number => {\n const value = o[key];\n if (typeof value !== \"number\" || !Number.isFinite(value)) throw new LicenseFormatError(`the licence payload is missing ${key}`);\n return value;\n };\n // `null` is the honest encoding of \"no end date\" — a sentinel year 9999 would\n // be a date somebody eventually compares against. Absent is still an error;\n // only an explicit null means never.\n const secondsOrNever = (key: string): number | null => {\n const value = o[key];\n if (value === null) return null;\n return seconds(key);\n };\n\n const plan = text(\"plan\");\n if (!PLANS.includes(plan as Plan)) {\n // A plan this SDK does not know is a newer commercial model, and refusing\n // to honour a licence we cannot classify would punish the customer for\n // buying something new. It is reported as unsupported so the developer is\n // told to upgrade.\n throw new LicenseFormatError(`this version of BroadPaper does not know the plan ${JSON.stringify(plan)}`, \"unsupported\");\n }\n\n // An evaluation licence is a term by definition. One with no end date could\n // only be a mistake, and honouring it would be a free licence forever.\n if (plan === \"evaluation\" && o.validUntil === null) {\n throw new LicenseFormatError(\"an evaluation licence must have an end date\");\n }\n\n const licenseType = text(\"licenseType\");\n if (!LICENSE_TYPES.includes(licenseType as LicenseType)) {\n throw new LicenseFormatError(`this version of BroadPaper cannot honour a ${JSON.stringify(licenseType)} licence`, \"unsupported\");\n }\n\n return {\n ...o,\n schemaVersion: 1,\n keyId: text(\"keyId\"),\n licenseId: text(\"licenseId\"),\n organisationId: text(\"organisationId\"),\n organisationName: text(\"organisationName\"),\n product: text(\"product\") as typeof PRODUCT,\n plan: plan as Plan,\n licenseType: licenseType as LicenseType,\n issuedAt: seconds(\"issuedAt\"),\n validUntil: secondsOrNever(\"validUntil\"),\n updatesUntil: seconds(\"updatesUntil\")\n };\n}\n\n/**\n * Encodes a payload into the text that gets signed.\n *\n * Exported because BroadPaper's own licence issuer must produce byte-identical\n * input to what the SDK verifies, and the only way to guarantee that is for\n * both to call this function.\n */\nexport function encodeSigningInput(payload: LicensePayload): { signedText: string; encodedPayload: string } {\n const encodedPayload = bytesToBase64Url(utf8ToBytes(JSON.stringify(payload)));\n return { encodedPayload, signedText: `${FORMAT_PREFIX}.${encodedPayload}` };\n}\n\n/** Assembles the final licence string from a payload and its signature. */\nexport function formatLicense(payload: LicensePayload, signature: Uint8Array): string {\n if (signature.length !== 64) throw new LicenseFormatError(`an Ed25519 signature is 64 bytes, this one is ${signature.length}`);\n const { encodedPayload } = encodeSigningInput(payload);\n return `${FORMAT_PREFIX}.${encodedPayload}.${bytesToBase64Url(signature)}`;\n}\n","/**\n * The public keys this build of the SDK will accept licence signatures from.\n *\n * **This file contains public keys only.** BroadPaper's private signing key\n * lives in the licence issuer's secret store and is never in this repository,\n * never in the npm package and never in a browser bundle. Publishing the\n * public half is the point: it is what lets a licence be checked with no\n * network call.\n *\n * Add a production key with:\n *\n * node scripts/licence-keys.mjs --production --key-id bp-1\n *\n * which prints the private key once, for the operator to paste into the\n * issuer's configuration, and writes the public half into this array.\n *\n * The list is an allowlist, so it is also the rotation mechanism: issue with a\n * new `keyId`, ship an SDK release trusting both, and drop the old entry once\n * every licence signed with it has expired. Licences carry their `keyId` inside\n * the signed payload, so an attacker cannot point one at a different key.\n *\n * It starts empty on purpose. A key here is trusted by every copy of the SDK in\n * the world, including production installs, which is exactly why a development\n * key must never be added to it — development keys are supplied at runtime\n * instead, through `configure({ trustedKeys })` or `BROADPAPER_TRUSTED_KEYS`,\n * so a licence signed in development cannot be honoured by a production build.\n */\nimport type { TrustedKey } from \"./types.js\";\n\nexport const TRUSTED_KEYS: readonly TrustedKey[] = [\n { keyId: \"bp-2026\", publicKey: \"woKmWyIazae7SpGhi4dwA7zNK1lWdboq88Rl5xscTs4\", note: \"production, generated 2026-09-08\" }\n];\n","/**\n * The licence certificate and the states it can be in.\n *\n * A BroadPaper licence is a signed certificate, not a secret. It says who\n * bought what and until when, BroadPaper signs it, and the SDK checks that\n * signature with a public key compiled into the package. Nothing here talks to\n * a network, reads a machine identifier or writes a file: a licence that is\n * valid on a laptop with no route to the internet is valid, full stop.\n */\n\n/**\n * Which licence was bought. Not a feature gate — both plans unlock exactly the\n * same software. The difference is who the resulting application may be put in\n * front of, which is a contractual matter and is presented as one in the\n * account area and the licence agreement.\n *\n * These are the `id`s in `shared/plans.json`, deliberately: that file is the\n * one machine-readable statement of the commercial model, and a certificate\n * that quotes a different vocabulary from the thing that priced it is a\n * certificate somebody eventually has to write a mapping table for.\n */\nexport type Plan = \"internal\" | \"saas\" | \"evaluation\";\n\n/**\n * The free, time-limited licence a developer gets from their account in\n * exchange for telling us who they are.\n *\n * It removes the mark while somebody evaluates, and nothing else: it permits no\n * production use, and it has no grace period — the day it ends, the mark comes\n * back. It is not in `shared/plans.json`, because nothing sells it.\n *\n * SDKs before 1.0 do not know the word and report such a licence as\n * `unsupported`, which keeps the mark and says to upgrade. That is the fail-safe\n * direction, and `tests/site/licence-format.test.ts` holds 0.4.1 to it.\n */\nexport const EVALUATION_PLAN = \"evaluation\" satisfies Plan;\n\n/**\n * How the licence is held.\n *\n * `subscription` is a term: the right to use the software ends with the term,\n * and so does the right to updates. It is the only thing sold today.\n *\n * `perpetual` is reserved. It would mean the right to use never ends, while\n * the right to updates still does — `validUntil` null, `updatesUntil` set. The\n * field exists now so that offering one later is a change to what the issuer\n * writes rather than a change to the certificate format, which would mean\n * reissuing every licence in the field. **This SDK does not honour one**: see\n * `readPayload` in `format.ts` for why refusing is the only safe answer.\n */\nexport type LicenseType = \"subscription\" | \"perpetual\";\n\n/** The product a licence is for. Present so one signing key can serve more later. */\nexport const PRODUCT = \"broadpaper-sdk\";\n\n/**\n * The signed payload.\n *\n * `organisationId` is the durable identifier of the buying organisation — a\n * Clerk organisation id — rather than an email address, because people change\n * addresses and organisations outlive their founders. It is *not* the Creem\n * customer id, which identifies the same buyer to the payment processor and is\n * a different string; the API keeps both, and naming this one for the\n * organisation is what stops the two being confused at a glance.\n *\n * Times are epoch **seconds**, not milliseconds and not ISO strings. The\n * certificate is written by `Utf8JsonWriter` in C# and read by `JSON.parse` in\n * TypeScript, and an integer cannot disagree about a timezone suffix or a\n * fractional second the way two date serialisers can — which matters more here\n * than anywhere, because the two halves only find out they disagree when a\n * customer's licence is rejected in the field.\n *\n * Field order is load-bearing on the issuing side only: `encodeSigningInput`\n * serialises this object, and `LicenceSigner.Payload` writes the same keys in\n * the same order so a certificate is byte-identical whichever issued it.\n * Verification never re-serialises — it checks the signature over the text as\n * received — so an unknown field from a later BroadPaper is carried through\n * rather than rejected.\n */\nexport interface LicensePayload {\n /**\n * Payload schema version. Bumped only when the meaning of a field changes,\n * never to add one: `readPayload` keeps fields it does not know about, so a\n * later BroadPaper can extend the certificate without older SDKs refusing it.\n */\n schemaVersion: 1;\n /** Which signing key signed this. Lets keys be rotated without reissuing everything. */\n keyId: string;\n /**\n * BroadPaper's own identifier for *this certificate*, `lic_…`.\n *\n * One per issuance, so a renewal or an upgrade carries a new one and the\n * document it produced can be dated from it. The buyer is identified by\n * `organisationId`, which does not change.\n */\n licenseId: string;\n /** The licensed organisation's durable id. A Clerk organisation id. */\n organisationId: string;\n /** The organisation's name, for display in warnings and PDF metadata. */\n organisationName: string;\n product: typeof PRODUCT;\n plan: Plan;\n licenseType: LicenseType;\n /** Epoch seconds. */\n issuedAt: number;\n /**\n * When the right to *use* the software ends, in epoch seconds.\n *\n * Null means never, which only a perpetual licence says. A subscription\n * always carries a date.\n */\n validUntil: number | null;\n /**\n * When the right to *updates* ends, in epoch seconds.\n *\n * For a subscription this equals `validUntil` and nothing reads it: the term\n * ends, and both rights end with it. It is separate from `validUntil` because\n * for a perpetual licence the two genuinely differ, and that is the whole\n * reason the field is here before anything consults it.\n */\n updatesUntil: number;\n}\n\n/**\n * Why a licence is not being honoured, or how it is being honoured.\n *\n * These are deliberately coarse. A developer needs to know whether their PDFs\n * will carry the evaluation mark and, if so, what to do about it; a taxonomy of\n * eleven parse failures helps nobody.\n */\nexport type LicenseState =\n /** No licence was configured. The SDK works; PDFs carry the evaluation mark. */\n | \"unlicensed\"\n /** A licence was configured but could not be read, or its signature is wrong. */\n | \"invalid\"\n /** Signed by a key this build of the SDK does not trust. */\n | \"untrusted\"\n /** For a different product, or a certificate this SDK is too old to honour. */\n | \"unsupported\"\n /** Valid, signed, and inside its term. */\n | \"active\"\n /** Past `validUntil` but inside the grace period: honoured, and warned about. */\n | \"grace\"\n /** Past `validUntil` and past the grace period. */\n | \"expired\";\n\n/** The outcome of checking a licence. Everything the SDK needs to decide. */\nexport interface LicenseStatus {\n state: LicenseState;\n /** True when PDFs render clean. `active` and `grace` only. */\n licensed: boolean;\n /** The payload, when one could be read and verified. */\n payload: LicensePayload | null;\n /** Which licence was bought, when it is being honoured. */\n plan: Plan | null;\n /** When the right to use ends, when known. Null for a licence with no end. */\n validUntil: Date | null;\n /** Days until `validUntil`; negative once expired. Null when there is no end date. */\n daysRemaining: number | null;\n /**\n * A developer-facing sentence. Always present for a state that is not\n * `active`, so callers can log it without composing their own prose.\n */\n message: string | null;\n /**\n * An informational line for a licence that *is* being honoured but is worth\n * knowing about — today, only an evaluation licence, which says when it ends.\n * Logged at info level, once. Optional so a status built by hand elsewhere\n * still type-checks.\n */\n notice?: string | null;\n}\n\n/** A public key this build of the SDK will accept signatures from. */\nexport interface TrustedKey {\n /** Matches `keyId` in the payload. */\n keyId: string;\n /** The raw 32-byte Ed25519 public key, base64url or base64. */\n publicKey: string;\n /** Free text for the key's provenance. Never displayed to end users. */\n note?: string;\n}\n","/**\n * Configuring BroadPaper once, for the life of the process.\n *\n * import { BroadPaper } from \"@broadpaper/forme\";\n * BroadPaper.configure({ license: process.env.BROADPAPER_LICENSE });\n *\n * The licence is not a credential, so this is not a secrets API: it is the same\n * shape of thing as setting a locale. A browser bundle that carries the licence\n * string in plain text is fine — the string is a signed certificate, and\n * possessing it grants nothing beyond what its payload already says.\n *\n * Configuration is deliberately process-global rather than an argument to every\n * render. Threading a licence through `renderPdf`, the render service, the\n * batch endpoint, the .NET client and every future export path is exactly the\n * kind of plumbing that ends with one path quietly forgetting it, and a\n * customer who has paid discovering a watermark in production.\n *\n * The store hangs off a `Symbol.for` key rather than a module-level variable so\n * that two copies of this package in one dependency tree — the usual result of\n * a mixed-version install — still agree about the licence.\n */\nimport { unlicensed, verifyLicense, type VerifyOptions } from \"./verify.js\";\nimport type { LicenseStatus, TrustedKey } from \"./types.js\";\n\nexport interface BroadPaperConfig {\n /**\n * The licence string, `BP1.…`. Read from `BROADPAPER_LICENSE` when this is\n * not set and the runtime has environment variables.\n */\n license?: string | null;\n /**\n * Public keys to trust in addition to the ones built into the SDK. This is\n * how a development licence is honoured on a developer's machine without a\n * development key ever being trusted by a production build.\n */\n trustedKeys?: readonly TrustedKey[];\n /** Overrides the 30-day post-expiry grace period. Mostly for tests. */\n gracePeriodDays?: number;\n /**\n * Where licence messages go. Defaults to `console.warn` for warnings and\n * `console.info` for information; `null` silences both. The level is a\n * second argument so a logger written for earlier versions still works.\n */\n logger?: ((message: string, level: LogLevel) => void) | null;\n}\n\n/** How loudly a licence message is said. */\nexport type LogLevel = \"info\" | \"warn\";\n\ninterface Store {\n config: BroadPaperConfig;\n /** Memoised by the licence string that produced it. */\n cache: { key: string; status: Promise<LicenseStatus> } | null;\n /** Messages already logged, so a hundred renders do not print a hundred lines. */\n logged: Set<string>;\n}\n\nconst STORE_KEY = Symbol.for(\"broadpaper.license.store\");\n\nfunction store(): Store {\n const g = globalThis as Record<symbol, unknown>;\n let s = g[STORE_KEY] as Store | undefined;\n if (!s) {\n s = { config: {}, cache: null, logged: new Set() };\n g[STORE_KEY] = s;\n }\n return s;\n}\n\n/**\n * Sets the licence and any other SDK-wide options. Call it once, at startup.\n *\n * Successive calls merge, so a host can set the licence in one place and the\n * grace period in another; passing `license: null` clears it.\n */\nexport function configure(config: BroadPaperConfig): void {\n const s = store();\n s.config = { ...s.config, ...config };\n s.cache = null;\n s.logged.clear();\n}\n\n/** What {@link configure} has been given, plus whatever the environment supplies. */\nexport function getConfig(): BroadPaperConfig {\n return { ...store().config };\n}\n\n/** Forgets the configuration entirely. Exists for tests. */\nexport function resetConfig(): void {\n const g = globalThis as Record<symbol, unknown>;\n delete g[STORE_KEY];\n}\n\n/**\n * `BroadPaper.configure({ … })`, for hosts that prefer a namespace to a bare\n * function. Both are the same store.\n */\nexport const BroadPaper = { configure, getConfig };\n\nfunction env(name: string): string | undefined {\n // Node, Deno and bundlers that inline `process.env` all satisfy this; a\n // browser without a shim simply has no environment, which is not an error.\n const p = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\n const value = p?.env?.[name];\n return value && value.trim() ? value : undefined;\n}\n\n/**\n * Keys trusted in addition to the built-in ones, from configuration and from\n * `BROADPAPER_TRUSTED_KEYS` — a JSON array of `{ keyId, publicKey }`.\n *\n * A syntax error there is reported rather than swallowed: someone setting that\n * variable is doing so on purpose, and silently ignoring it would present as\n * \"my development licence is not being honoured\" with nothing to go on.\n */\nfunction trustedKeysFromEnv(): readonly TrustedKey[] {\n const raw = env(\"BROADPAPER_TRUSTED_KEYS\");\n if (!raw) return [];\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) throw new Error(\"expected a JSON array\");\n return parsed.map((entry, i) => {\n const k = entry as Record<string, unknown>;\n if (typeof k?.keyId !== \"string\" || typeof k?.publicKey !== \"string\") throw new Error(`entry ${i} needs keyId and publicKey`);\n return { keyId: k.keyId, publicKey: k.publicKey };\n });\n } catch (e) {\n warn(`BROADPAPER_TRUSTED_KEYS could not be read (${(e as Error).message}); it is being ignored.`);\n return [];\n }\n}\n\n/** Logs a message once, whatever it takes to reach the developer. */\nexport function warn(message: string): void {\n log(message, \"warn\");\n}\n\n/** Logs an informational message once. */\nexport function info(message: string): void {\n log(message, \"info\");\n}\n\nfunction log(message: string, level: LogLevel): void {\n const s = store();\n if (s.logged.has(message)) return;\n s.logged.add(message);\n const logger = s.config.logger;\n if (logger === null) return;\n const line = `[BroadPaper] ${message}`;\n if (logger) logger(line, level);\n else if (level === \"info\") console.info(line);\n else console.warn(line);\n}\n\nfunction report(status: LicenseStatus): void {\n if (status.message) warn(status.message);\n if (status.notice) info(status.notice);\n}\n\nexport interface ResolveOptions extends VerifyOptions {\n /** A licence for this call only, overriding the configured one. */\n license?: string | null;\n}\n\n/**\n * The licence status for the current configuration.\n *\n * Memoised on the licence string, because every PDF asks and a signature check\n * per page of a five-hundred-page batch is waste. `configure` clears it, and a\n * per-call `license` bypasses it.\n *\n * Whatever the answer, any developer-facing message is logged once. That is the\n * only thing the SDK does that a caller did not ask for, and it is the\n * difference between a developer seeing \"your licence expired\" and a developer\n * seeing an unexplained watermark.\n */\nexport async function resolveLicenseStatus(options: ResolveOptions = {}): Promise<LicenseStatus> {\n const s = store();\n const config = s.config;\n const license = options.license !== undefined ? options.license : config.license ?? env(\"BROADPAPER_LICENSE\") ?? null;\n const trustedKeys = [...(options.trustedKeys ?? []), ...(config.trustedKeys ?? []), ...trustedKeysFromEnv()];\n const gracePeriodDays = options.gracePeriodDays ?? config.gracePeriodDays;\n\n if (!license) {\n const status = unlicensed();\n report(status);\n return status;\n }\n\n const verify = (): Promise<LicenseStatus> =>\n verifyLicense(license, { ...(options.now ? { now: options.now } : {}), trustedKeys, ...(gracePeriodDays !== undefined ? { gracePeriodDays } : {}) }).then((status) => {\n report(status);\n return status;\n });\n\n // A fixed clock is a test or a deterministic render; caching across different\n // clocks would make the grace-period boundary depend on call order.\n if (options.now || options.license !== undefined) return verify();\n\n const key = `${license}|${trustedKeys.map((k) => k.keyId).join(\",\")}|${gracePeriodDays ?? \"\"}`;\n if (s.cache?.key !== key) s.cache = { key, status: verify() };\n return s.cache.status;\n}\n","/**\n * The evaluation mark.\n *\n * This is the only thing an unlicensed BroadPaper does differently. Every\n * block, both PDF backends, the designer, data binding, pagination and the\n * render service behave identically with and without a licence; what changes is\n * that the file says, on every page, that it was produced with an evaluation\n * copy.\n *\n * It is drawn to be unmistakable in a document review and harmless to a\n * developer evaluating the product: a light diagonal line across the page,\n * behind nothing and over everything, at an opacity that leaves the report\n * entirely readable. Making the output illegible would only push people towards\n * evaluating something else.\n *\n * Each backend draws the spec with its own primitives — the Forme engine has a\n * page-native watermark node, Chromium gets a printed overlay — because a\n * single drawing implementation would mean one of them post-processing a file\n * the other had already finished, and a stamping pass that can be skipped is a\n * stamping pass that eventually is.\n */\nimport { resolveLicenseStatus, type ResolveOptions } from \"./config.js\";\nimport type { LicenseStatus } from \"./types.js\";\n\nexport interface WatermarkSpec {\n text: string;\n /** Points. The engines size the mark from this, not from the page. */\n fontSize: number;\n /** Degrees, anticlockwise from horizontal. */\n angle: number;\n /** Hex, without alpha. */\n color: string;\n /** 0–1. */\n opacity: number;\n /**\n * Where on the page it goes.\n *\n * `center` is the evaluation mark's diagonal stamp: sized from `fontSize`,\n * rotated by `angle`, across the middle of every page. It is the default and\n * the only thing `watermarkFor` ever asks for.\n *\n * `bottom` is a credit line along the foot of the page, in the bottom margin —\n * the shape a host's own commercial model usually wants. BroadPaper Cloud's\n * free plan started as a `center` mark at eight points, which is a small grey\n * sentence floating in the middle of an otherwise finished invoice: it reads\n * as a mistake rather than as a credit, and the whole point of that mark is\n * that a free user's document stays sendable.\n *\n * It is drawn in the margin rather than in the footer band deliberately, so it\n * costs no layout: a mark that changed the content height would make the\n * finished file paginate differently from the canvas the user approved.\n */\n placement?: \"center\" | \"bottom\";\n /**\n * A small mark drawn before the text, as an `image/svg+xml` data URI. `bottom`\n * only.\n *\n * Vector rather than a bitmap because it is drawn at about nine points and a\n * raster at that size is a smudge. Both backends parse the URI themselves —\n * the Forme engine's image decoder cannot read an SVG, so it is re-emitted as\n * the drawing it is.\n */\n mark?: string;\n /**\n * Where the text links to, making it a link in the file. `bottom` only, and\n * the Forme backend only — the Chromium backend stamps text into a finished\n * file and draws it unlinked.\n *\n * For a host's own credit line, whose job is to be followed: a free document a\n * business sends a client is the most-read advert the host will ever have, and\n * an address nobody can click is one hardly anybody types. The evaluation\n * stamp never has one, because it is there to be seen, not visited; its\n * footnote does, because saying where to get a key is the footnote's whole job.\n */\n href?: string;\n /**\n * A second mark drawn with this one: a `bottom` line under a `center` stamp.\n *\n * The evaluation mark carries one that says how to remove it. It is a field\n * of the spec rather than a second return value of `watermarkFor` so that\n * everything already passing one spec around — Cloud, a host's own extra mark,\n * both backends — carries it without learning a new shape. Backends reach it\n * through {@link expandWatermarks}.\n */\n footnote?: WatermarkSpec;\n}\n\n/**\n * The marks to draw, with each spec's `footnote` beside it and the empty\n * slots dropped. What every backend iterates.\n */\nexport function expandWatermarks(marks: ReadonlyArray<WatermarkSpec | null | undefined>): WatermarkSpec[] {\n const out: WatermarkSpec[] = [];\n for (const mark of marks) {\n if (!mark) continue;\n out.push(mark);\n if (mark.footnote) out.push(mark.footnote);\n }\n return out;\n}\n\n/**\n * The mark itself.\n *\n * No dashes or typographic punctuation: the standard PDF faces carry no em\n * dash, and a theme that names a font the engine does not have falls back to\n * them, which turned \"BroadPaper — Evaluation Copy\" into \"BroadPaper\n * Evaluation Copy\" in half the documents and a question mark in the rest.\n */\nexport const EVALUATION_WATERMARK: WatermarkSpec = {\n text: \"BroadPaper Evaluation Copy\",\n fontSize: 44,\n angle: -35,\n color: \"#c8102e\",\n opacity: 0.12,\n /*\n * The line that says what to do about the stamp. The stamp itself is never a\n * link — it is there to be seen. This line is the opposite: it exists to be\n * followed, so the Forme backend links it; Chromium stamps text into a finished\n * file and draws it unlinked. Darker than the stamp because at eight points\n * the stamp's opacity is unreadable, and still grey enough to leave the page\n * a page. A full stop rather than a dash, for the reason above.\n */\n footnote: {\n text: \"Evaluation copy. Remove this mark with a free key: broadpaper.com/eval\",\n fontSize: 8,\n angle: 0,\n color: \"#6b6b6b\",\n opacity: 0.9,\n placement: \"bottom\",\n href: \"https://www.broadpaper.com/eval\"\n }\n};\n\n/**\n * The mark a render should carry, or null when the licence says none.\n *\n * Backends call this rather than reading the licence status themselves, so\n * \"licensed\" and \"no watermark\" cannot drift apart.\n */\nexport async function resolveWatermark(options: ResolveOptions = {}): Promise<WatermarkSpec | null> {\n const status = await resolveLicenseStatus(options);\n return watermarkFor(status);\n}\n\n/** The pure half of {@link resolveWatermark}, for callers that already have a status. */\nexport function watermarkFor(status: LicenseStatus): WatermarkSpec | null {\n return status.licensed ? null : EVALUATION_WATERMARK;\n}\n\n/**\n * CSS that paints the mark on every `.bp-page` of a rendered document.\n *\n * Used by the Chromium backend and by the designer's print path — the two\n * places a PDF is produced by a browser rather than by the engine. `position:\n * fixed` would put it on the viewport rather than on each page, so it is an\n * absolutely positioned pseudo-element on the page box, which prints once per\n * sheet.\n *\n * `print-color-adjust` matters: without it Chromium drops the mark from the\n * printed file exactly as it drops any other background colour.\n */\nexport function watermarkCss(spec: WatermarkSpec = EVALUATION_WATERMARK): string {\n const note = spec.footnote;\n return [\n \".bp-page{position:relative;}\",\n \".bp-page::after{\",\n `content:\"${cssString(spec.text)}\";`,\n \"position:absolute;inset:0;\",\n \"display:flex;align-items:center;justify-content:center;\",\n \"pointer-events:none;z-index:2147483647;\",\n `transform:rotate(${spec.angle}deg);`,\n \"font-family:Helvetica,Arial,sans-serif;font-weight:700;letter-spacing:0.04em;white-space:nowrap;\",\n `font-size:${spec.fontSize}pt;color:${spec.color};opacity:${spec.opacity};`,\n \"-webkit-print-color-adjust:exact;print-color-adjust:exact;\",\n \"}\",\n // The footnote, along the foot of the same page box. Unlinked here: a\n // pseudo-element cannot be an anchor.\n ...(note\n ? [\n \".bp-page::before{\",\n `content:\"${cssString(note.text)}\";`,\n \"position:absolute;left:0;right:0;bottom:4pt;text-align:center;\",\n \"pointer-events:none;z-index:2147483647;\",\n \"font-family:Helvetica,Arial,sans-serif;white-space:nowrap;\",\n `font-size:${note.fontSize}pt;color:${note.color};opacity:${note.opacity};`,\n \"-webkit-print-color-adjust:exact;print-color-adjust:exact;\",\n \"}\"\n ]\n : [])\n ].join(\"\");\n}\n\nfunction cssString(text: string): string {\n return text.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n}\n\n/**\n * The PDF Producer string for a render.\n *\n * Licensed output records the licence id. It is not a secret — it identifies a\n * customer's licence, not their account — and having it in the file means a\n * document that turns up somewhere it should not can be traced back to the\n * licence that produced it without any of the tracking a phone-home would need.\n */\nexport function producerString(status: LicenseStatus): string {\n if (!status.licensed || !status.payload) return \"BroadPaper (evaluation)\";\n return `BroadPaper (${status.payload.licenseId})`;\n}\n"],"mappings":"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","names":["i"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@broadpaper/license",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "BroadPaper licence certificates: offline Ed25519 verification, entitlement states and the evaluation watermark. Contains only the public verification keys.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pdf",
|