@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 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 9 September 2026.
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, and a licence removes it. Contractually, the licence is also what permits
45
- production use. No seats, no activation, no machine binding, no telemetry, and no call home at
46
- any point.
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
- /** Overrides {@link DEFAULT_GRACE_PERIOD_DAYS}. */
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
- /** Where licence warnings go. Defaults to `console.warn`. */
221
- logger?: ((message: string) => void) | null;
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 mark
374
- * never has one — `watermarkFor` does not set it — because that mark is there to
375
- * be seen, not visited.
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: "No BroadPaper licence is configured, so PDFs carry the evaluation watermark. Everything else works; see https://www.broadpaper.com/docs/guide/licensing."
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 graceDays = options.gracePeriodDays ?? DEFAULT_GRACE_PERIOD_DAYS;
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 ${validUntil.toISOString().slice(0, 10)} and its ${graceDays}-day grace period has passed, so PDFs carry the evaluation watermark. Renewing issues a new licence in your BroadPaper account.`,
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
- if (status.message) warn(status.message);
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
- if (status.message) warn(status.message);
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 text = spec.text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
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.4.1",
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",