layoutbridge-light 0.0.0-stage → 1.0.1

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.
Files changed (92) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/LICENSE +26 -0
  3. package/README.de.md +367 -0
  4. package/README.md +353 -2
  5. package/THIRD-PARTY-NOTICES.md +2878 -0
  6. package/dist/src/mcp/catalog.js +131 -0
  7. package/dist/src/mcp/config.js +211 -0
  8. package/dist/src/mcp/content-credentials.js +65 -0
  9. package/dist/src/mcp/dispatch.js +171 -0
  10. package/dist/src/mcp/epub-check.js +331 -0
  11. package/dist/src/mcp/epub-orientation.js +132 -0
  12. package/dist/src/mcp/license/free-tools.js +16 -0
  13. package/dist/src/mcp/license/gate.js +210 -0
  14. package/dist/src/mcp/license/index.js +107 -0
  15. package/dist/src/mcp/license/instance-name.js +57 -0
  16. package/dist/src/mcp/license/key-source.js +59 -0
  17. package/dist/src/mcp/license/lemonsqueezy-verifier.js +207 -0
  18. package/dist/src/mcp/license/placeholder-verifier.js +10 -0
  19. package/dist/src/mcp/license/polar-ids.js +100 -0
  20. package/dist/src/mcp/license/polar-verifier.js +285 -0
  21. package/dist/src/mcp/license/product-ids.js +73 -0
  22. package/dist/src/mcp/license/state-store.js +91 -0
  23. package/dist/src/mcp/license/types.js +11 -0
  24. package/dist/src/mcp/logging.js +271 -0
  25. package/dist/src/mcp/pdf-postprocess.js +798 -0
  26. package/dist/src/mcp/product-info.js +54 -0
  27. package/dist/src/mcp/product.js +56 -0
  28. package/dist/src/mcp/server.js +253 -0
  29. package/dist/src/mcp/setup.js +680 -0
  30. package/dist/src/mcp/start.cjs +117 -0
  31. package/dist/src/mcp/target-folder.js +25 -0
  32. package/dist/src/mcp/tool-definition.js +2 -0
  33. package/dist/src/mcp/tools.js +2680 -0
  34. package/dist/src/mcp/typography-rules.js +70 -0
  35. package/dist/src/transport/com.js +436 -0
  36. package/dist/src/transport/osascript.js +171 -0
  37. package/dist/src/transport/queue.js +19 -0
  38. package/dist/src/transport/script-bundle.js +92 -0
  39. package/dist/src/transport/transport.js +52 -0
  40. package/indesign/commands/10-app.js +6 -0
  41. package/indesign/commands/20-documents.js +517 -0
  42. package/indesign/commands/22-document-setup.js +204 -0
  43. package/indesign/commands/23-save-package.js +258 -0
  44. package/indesign/commands/25-export.js +695 -0
  45. package/indesign/commands/26-export-image.js +202 -0
  46. package/indesign/commands/27-textdefaults.js +151 -0
  47. package/indesign/commands/28-selection.js +174 -0
  48. package/indesign/commands/30-pages.js +1032 -0
  49. package/indesign/commands/31-view.js +310 -0
  50. package/indesign/commands/32-sections.js +203 -0
  51. package/indesign/commands/35-parents.js +448 -0
  52. package/indesign/commands/37-layout.js +837 -0
  53. package/indesign/commands/40-frames.js +886 -0
  54. package/indesign/commands/41-shapes.js +505 -0
  55. package/indesign/commands/42-groups.js +117 -0
  56. package/indesign/commands/43-layers.js +310 -0
  57. package/indesign/commands/44-effects.js +274 -0
  58. package/indesign/commands/45-frame-options.js +247 -0
  59. package/indesign/commands/47-anchored.js +473 -0
  60. package/indesign/commands/50-modify.js +803 -0
  61. package/indesign/commands/52-align.js +139 -0
  62. package/indesign/commands/55-fit.js +106 -0
  63. package/indesign/commands/60-images.js +1154 -0
  64. package/indesign/commands/62-embed.js +120 -0
  65. package/indesign/commands/69-placeholders.js +132 -0
  66. package/indesign/commands/70-text.js +710 -0
  67. package/indesign/commands/71-outlines.js +92 -0
  68. package/indesign/commands/72-place-text.js +476 -0
  69. package/indesign/commands/73-story-lock.js +157 -0
  70. package/indesign/commands/74-stories.js +121 -0
  71. package/indesign/commands/75-tables.js +702 -0
  72. package/indesign/commands/76-text-variables.js +260 -0
  73. package/indesign/commands/77-story-options.js +86 -0
  74. package/indesign/commands/78-frame-lines.js +511 -0
  75. package/indesign/commands/79-check-typography.js +480 -0
  76. package/indesign/commands/80-styles.js +1960 -0
  77. package/indesign/commands/81-composition.js +355 -0
  78. package/indesign/commands/82-object-styles.js +263 -0
  79. package/indesign/commands/83-drop-caps.js +87 -0
  80. package/indesign/commands/83-indents-tabs.js +264 -0
  81. package/indesign/commands/84-import-styles.js +142 -0
  82. package/indesign/commands/85-swatches.js +112 -0
  83. package/indesign/commands/86-gradients.js +185 -0
  84. package/indesign/commands/87-replace-swatch.js +180 -0
  85. package/indesign/commands/88-replace-font.js +212 -0
  86. package/indesign/commands/89-toc-index.js +1002 -0
  87. package/indesign/commands/90-print-on-demand.js +495 -0
  88. package/indesign/commands/91-epub.js +629 -0
  89. package/indesign/commands/92-accessibility.js +1131 -0
  90. package/indesign/interpreter.js +829 -0
  91. package/package.json +41 -4
  92. package/rules/typography-rules.json +32 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Hinterlegte Kennungen des Zahlungsdienstes Polar (Auftrag A-15 Nr. 2,
3
+ * 23.09.2026).
4
+ *
5
+ * Polar arbeitet mit UUIDs statt Zahlen: die Organisation
6
+ * (organization_id) gehoert in jeden Aufruf, der Benefit (benefit_id)
7
+ * benennt den Lizenzschluessel-Vorteil des Produkts. Store, Produkt und
8
+ * Variante wie bei lemonsqueezy gibt es hier nicht; die Kennungen von
9
+ * lemonsqueezy in product-ids.ts bleiben davon unberuehrt.
10
+ *
11
+ * Hinterlegt sind die Kennungen des Live-Kontos (Organisation "MCP
12
+ * Interfaces"), wie sie das Auftragsdokument unter A-15a Teil 2
13
+ * (23.09.2026, 07:14 Uhr) nennt: organization_id aus Settings >
14
+ * Preferences (Feld "Identifier"), benefit_id aus der Adresse der
15
+ * Benefit-Seite (Vermerk E-85). Bestaetigt durch die erste validate-Antwort
16
+ * mit dem Live-Schluessel fuer den Arbeitsplatz des Anbieters (Nachtrag 23.09.2026,
17
+ * 08:29 Uhr) und die Live-Probe von A-9 (Vermerk E-86): beide Werte
18
+ * stimmen mit der Antwort ueberein.
19
+ *
20
+ * Fuer die Sandbox-Probe lassen sie sich von aussen ueberschreiben – ueber
21
+ * Umgebungsvariablen und ausschliesslich in der Bauphase, nie ueber
22
+ * config.json: config.json ist Kundenkonfiguration und koennte damit den
23
+ * Produktabgleich aushebeln.
24
+ */
25
+ import { envName, PRODUCT, PRODUCTS } from '../product.js';
26
+ /** Kennungen des Live-Kontos (A-15a Teil 2, 23.09.2026) – Vollprodukt. */
27
+ export const POLAR_IDS = {
28
+ organization_id: 'e8d80621-fb90-45e2-b0b6-73a94271fe18',
29
+ benefit_id: 'e9b61756-779b-4d65-83b9-f398346f6184',
30
+ };
31
+ /**
32
+ * Kennungen von LayoutBridge Light (A-70, E-109): dieselbe Organisation,
33
+ * eigener Benefit des eigenen Polar-Produkts ("LayoutBridge Light licence
34
+ * key", Schluesselpraefix LBL, 1 Aktivierung, kein Ablauf; Angabe des Anbieters
35
+ * 01.11.2026, Nachtrag E-109). Ohne Benefit wiese Light jeden Schluessel ab
36
+ * (requireBenefit im Verifier), und scripts/light-package.mjs baute kein
37
+ * Paket.
38
+ */
39
+ export const LIGHT_POLAR_IDS = {
40
+ organization_id: 'e8d80621-fb90-45e2-b0b6-73a94271fe18',
41
+ benefit_id: 'ce5f2f90-9ed9-4306-8928-f010db5ad740',
42
+ };
43
+ /** Hinterlegte Kennungen je Produkt. */
44
+ export function polarIdsFor(product = PRODUCT) {
45
+ return product.id === 'light' ? LIGHT_POLAR_IDS : POLAR_IDS;
46
+ }
47
+ /** Name der Umgebungsvariablen der Bauphase (Vollprodukt; Light mit eigenem Praefix). */
48
+ export const POLAR_ORG_ID_ENV = 'LAYOUTBRIDGE_SPM_LICENSE_POLAR_ORG_ID';
49
+ export const POLAR_BENEFIT_ID_ENV = 'LAYOUTBRIDGE_SPM_LICENSE_POLAR_BENEFIT_ID';
50
+ export function polarOrgIdEnv(product = PRODUCT) {
51
+ return envName('LICENSE_POLAR_ORG_ID', product);
52
+ }
53
+ export function polarBenefitIdEnv(product = PRODUCT) {
54
+ return envName('LICENSE_POLAR_BENEFIT_ID', product);
55
+ }
56
+ /**
57
+ * Benefits der jeweils anderen Produkte. Eine Umgebungsvariable der
58
+ * Bauphase darf nie auf einen davon zeigen – sonst schaltete ein Schluessel
59
+ * von Light ueber die Variable das Vollprodukt frei (E-109).
60
+ */
61
+ export function foreignBenefitIds(product = PRODUCT) {
62
+ return PRODUCTS.filter((other) => other.id !== product.id)
63
+ .map((other) => polarIdsFor(other).benefit_id)
64
+ .filter((id) => id !== null)
65
+ .map((id) => id.toLowerCase());
66
+ }
67
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
68
+ /** Nur eine UUID zaehlt als Kennung; alles andere gilt als nicht gesetzt. */
69
+ function uuidFromEnv(value) {
70
+ if (value === undefined)
71
+ return null;
72
+ const trimmed = value.trim();
73
+ if (!UUID.test(trimmed))
74
+ return null;
75
+ return trimmed.toLowerCase();
76
+ }
77
+ /**
78
+ * Kennungen fuer diesen Lauf: die hinterlegten Konstanten, einzeln
79
+ * ueberschreibbar durch Umgebungsvariablen der Bauphase.
80
+ */
81
+ export function resolvePolarIds(env = process.env, base, product = PRODUCT, foreign = foreignBenefitIds(product)) {
82
+ const hinterlegt = base ?? polarIdsFor(product);
83
+ const benefitOverride = uuidFromEnv(env[polarBenefitIdEnv(product)]);
84
+ const allowedBenefit = benefitOverride !== null && !foreign.map((id) => id.toLowerCase()).includes(benefitOverride) ? benefitOverride : null;
85
+ return {
86
+ organization_id: uuidFromEnv(env[polarOrgIdEnv(product)]) ?? hinterlegt.organization_id,
87
+ benefit_id: allowedBenefit ?? hinterlegt.benefit_id,
88
+ };
89
+ }
90
+ /** Ist ueberhaupt eine Kennung hinterlegt? */
91
+ export function polarIdsConfigured(ids) {
92
+ return ids.organization_id !== null || ids.benefit_id !== null;
93
+ }
94
+ /** Kurzfassung fuer Startzeile und Protokoll. */
95
+ export function describePolarIds(ids) {
96
+ if (!polarIdsConfigured(ids))
97
+ return 'not set';
98
+ return `organization ${ids.organization_id ?? '-'}, benefit ${ids.benefit_id ?? '-'}`;
99
+ }
100
+ //# sourceMappingURL=polar-ids.js.map
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Prueffunktion des Zahlungsdienstes Polar (Auftrag A-15, 23.09.2026;
3
+ * Beobachtungen aus der Sandbox in Polar-Pruefung_2026-09-23.md, 3b).
4
+ *
5
+ * Gebaut wie lemonsqueezy-verifier.ts und **neben** dieser Klasse: drei
6
+ * Endpunkte des Kundenportals – activate, validate, deactivate –, alle ohne
7
+ * Anmeldung und damit ohne Geheimnis im Connector, Rumpf JSON, kein
8
+ * Authorization-Header. Kein SDK, keine neue Laufzeitabhaengigkeit; der
9
+ * Aufruf geht ueber fetch aus Node ab Version 20, Zeitueberschreitung 10 s.
10
+ *
11
+ * Unterschiede zu lemonsqueezy, die den Bau bestimmen:
12
+ * - Kennungen sind UUIDs (organization_id, benefit_id) statt Zahlen.
13
+ * - Zustaende heissen granted / revoked / disabled.
14
+ * - Die Aktivierung kommt bei activate als `id` (nicht `instance.id`);
15
+ * validate traegt die Felder des Schluessels unmittelbar im Rumpf und die
16
+ * Aktivierung in `activation`.
17
+ * - Gesperrt oder gekuendigt kommt als HTTP 404 mit eigenem Wortlaut, nicht
18
+ * als Zustand; deactivate antwortet mit HTTP 204 ohne Rumpf.
19
+ * - Ohne Anmeldung gelten 3 Aufrufe je Sekunde; HTTP 429 zaehlt als
20
+ * "nicht erreichbar", und zwischen zwei eigenen Aufrufen liegt eine Pause.
21
+ *
22
+ * Aufrufmuster (A-15 Nr. 4): ohne gespeicherte Aktivierung aktivieren, sonst
23
+ * mit activation_id pruefen. Kommt 404 "Not found" (die Aktivierung wurde
24
+ * beim Haendler oder vom Kunden im Portal entfernt), genau einmal neu
25
+ * aktivieren; scheitert das, bleibt es beim Ergebnis dieses Versuchs.
26
+ *
27
+ * Die Antworten enthalten Kundendaten (`customer`, `user`). Sie werden
28
+ * weder uebernommen noch protokolliert (A-8); nach aussen gehen nur
29
+ * Zustand, Ablauf und die Aktivierungskennung. Der Lizenzschluessel
30
+ * erscheint in keiner Meldung.
31
+ */
32
+ import { describePolarIds, polarIdsConfigured, } from './polar-ids.js';
33
+ import { SUPPORT_EMAIL } from '../product-info.js';
34
+ import { envName, PRODUCT } from '../product.js';
35
+ export const DEFAULT_BASE_URL = 'https://api.polar.sh';
36
+ /** Sandbox; nur in der Bauphase, ueber die Umgebungsvariable unten. */
37
+ export const SANDBOX_BASE_URL = 'https://sandbox-api.polar.sh';
38
+ export const POLAR_SANDBOX_ENV = 'LAYOUTBRIDGE_SPM_LICENSE_POLAR_SANDBOX';
39
+ export function polarSandboxEnv(product = PRODUCT) {
40
+ return envName('LICENSE_POLAR_SANDBOX', product);
41
+ }
42
+ export const DEFAULT_TIMEOUT_MS = 10_000;
43
+ /** Abstand vor einem zweiten eigenen Aufruf (Grenze des Dienstes: 3/s). */
44
+ export const DEFAULT_RETRY_DELAY_MS = 1_000;
45
+ function record(value) {
46
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
47
+ return null;
48
+ return value;
49
+ }
50
+ function text(value) {
51
+ return typeof value === 'string' && value.length > 0 ? value : null;
52
+ }
53
+ /** Einen Satz sauber abschliessen, damit zwei Saetze nicht zusammenlaufen. */
54
+ function sentence(value) {
55
+ const trimmed = value.trimEnd();
56
+ return /[.!?]$/.test(trimmed) ? trimmed : trimmed + '.';
57
+ }
58
+ /** Den Schluessel aus jedem Text entfernen, der nach aussen geht. */
59
+ export function redactKey(message, key) {
60
+ if (key.length === 0)
61
+ return message;
62
+ return message.split(key).join('<key>');
63
+ }
64
+ /** Basisadresse fuer diesen Lauf: live, in der Bauphase auf Wunsch Sandbox. */
65
+ export function resolvePolarBaseUrl(env = process.env, product = PRODUCT) {
66
+ return env[polarSandboxEnv(product)] === '1' ? SANDBOX_BASE_URL : DEFAULT_BASE_URL;
67
+ }
68
+ export class PolarVerifier {
69
+ name = 'polar';
70
+ ids;
71
+ baseUrl;
72
+ timeoutMs;
73
+ fetchImpl;
74
+ now;
75
+ retryDelayMs;
76
+ requireBenefit;
77
+ constructor(options) {
78
+ this.ids = options.ids;
79
+ this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
80
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
81
+ this.fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
82
+ this.now = options.now ?? (() => new Date());
83
+ this.retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
84
+ this.requireBenefit = options.requireBenefit === true;
85
+ }
86
+ /** Eine Zeile fuer die Startmeldung des Servers. */
87
+ describe() {
88
+ return `${this.name} (${this.baseUrl}, ids: ${describePolarIds(this.ids)})`;
89
+ }
90
+ async verify(key, context) {
91
+ if (this.requireBenefit && this.ids.benefit_id === null) {
92
+ return this.outcome('product_mismatch', 'This build has no product identification for the licence service, so no key can be accepted.', key, null, context.instance_id);
93
+ }
94
+ if (context.instance_id === null) {
95
+ return this.interpret(await this.activate(key, context.instance_name), key);
96
+ }
97
+ const validated = await this.call('validate', { key, activation_id: context.instance_id });
98
+ const outcome = this.interpret(validated, key, context.instance_id);
99
+ if (outcome.status !== 'invalid' || !this.activationLikelyGone(validated))
100
+ return outcome;
101
+ /* Aktivierung entfernt: genau ein Aktivierungsversuch, mit Abstand. */
102
+ await this.pause();
103
+ return this.interpret(await this.activate(key, context.instance_name), key);
104
+ }
105
+ async deactivate(key, instanceId) {
106
+ const answer = await this.call('deactivate', { key, activation_id: instanceId });
107
+ if (answer.httpStatus === 204)
108
+ return;
109
+ if (answer.body === null) {
110
+ throw new Error('The licence service could not be reached: ' + answer.reason);
111
+ }
112
+ const reason = text(answer.body['detail']) ??
113
+ text(answer.body['error']) ??
114
+ 'the service refused the deactivation (HTTP ' + String(answer.httpStatus) + ')';
115
+ throw new Error(redactKey(reason, key));
116
+ }
117
+ activate(key, label) {
118
+ return this.call('activate', { key, label });
119
+ }
120
+ /**
121
+ * Der Schluessel selbst ist noch brauchbar, nur die Aktivierung nicht
122
+ * mehr: HTTP 404 mit dem Wortlaut "Not found" (Sandbox 23.09.2026). Der
123
+ * eigene Wortlaut fuer einen erloschenen Schluessel ("no longer active")
124
+ * gehoert nicht dazu – dort waere ein zweiter Aufruf sinnlos.
125
+ */
126
+ activationLikelyGone(answer) {
127
+ if (answer.httpStatus !== 404 || answer.body === null)
128
+ return false;
129
+ if (this.keyGone(answer.body))
130
+ return false;
131
+ return true;
132
+ }
133
+ /** 404 mit dem Hinweis, dass der Schluessel selbst erloschen ist. */
134
+ keyGone(body) {
135
+ const detail = text(body['detail']);
136
+ return detail !== null && /no longer active/i.test(detail);
137
+ }
138
+ pause() {
139
+ if (this.retryDelayMs <= 0)
140
+ return Promise.resolve();
141
+ return new Promise((resolve) => {
142
+ setTimeout(resolve, this.retryDelayMs);
143
+ });
144
+ }
145
+ async call(endpoint, fields) {
146
+ /* organization_id gehoert in jeden Aufruf, sobald sie hinterlegt ist. */
147
+ const payload = { ...fields };
148
+ if (this.ids.organization_id !== null)
149
+ payload['organization_id'] = this.ids.organization_id;
150
+ let response;
151
+ try {
152
+ response = await this.fetchImpl(`${this.baseUrl}/v1/customer-portal/license-keys/${endpoint}`, {
153
+ method: 'POST',
154
+ headers: {
155
+ Accept: 'application/json',
156
+ 'Content-Type': 'application/json',
157
+ },
158
+ body: JSON.stringify(payload),
159
+ signal: AbortSignal.timeout(this.timeoutMs),
160
+ });
161
+ }
162
+ catch (error) {
163
+ return { body: null, httpStatus: null, endpoint, reason: 'request failed (' + String(error) + ')' };
164
+ }
165
+ const httpStatus = response.status;
166
+ /* Erfolg ohne Rumpf: deactivate antwortet mit 204. */
167
+ if (httpStatus === 204) {
168
+ return { body: {}, httpStatus, endpoint, reason: '' };
169
+ }
170
+ if (httpStatus >= 500 || httpStatus === 429) {
171
+ return { body: null, httpStatus, endpoint, reason: 'the service answered HTTP ' + httpStatus };
172
+ }
173
+ let parsed;
174
+ try {
175
+ parsed = await response.json();
176
+ }
177
+ catch {
178
+ return { body: null, httpStatus, endpoint, reason: 'the answer was not readable JSON (HTTP ' + httpStatus + ')' };
179
+ }
180
+ const asRecord = record(parsed);
181
+ if (asRecord === null) {
182
+ return { body: null, httpStatus, endpoint, reason: 'the answer was not a JSON object (HTTP ' + httpStatus + ')' };
183
+ }
184
+ return { body: asRecord, httpStatus, endpoint, reason: '' };
185
+ }
186
+ /** Antwort des Dienstes auf die Ergebnisse aus S11 abbilden (A-15 Nr. 4). */
187
+ interpret(answer, key, storedActivationId = null) {
188
+ if (answer.body === null) {
189
+ return { status: 'unreachable', message: 'The licence service could not be reached: ' + answer.reason };
190
+ }
191
+ const body = answer.body;
192
+ /* activate antwortet mit einem license_key-Block, validate mit den
193
+ Feldern des Schluessels unmittelbar im Rumpf. */
194
+ const licenseKey = record(body['license_key']) ?? body;
195
+ const detail = text(body['detail']);
196
+ const expiresAt = text(licenseKey['expires_at']);
197
+ const activationId = this.activationId(answer, storedActivationId);
198
+ /* 1. Produkt vor allem anderen – auch wenn der Dienst "gueltig" sagt. */
199
+ const mismatch = this.productMismatch(licenseKey, answer.httpStatus);
200
+ if (mismatch !== null) {
201
+ return this.outcome('product_mismatch', mismatch, key, expiresAt, activationId);
202
+ }
203
+ /* 2. Abgelaufen, gesperrt oder erloschen. */
204
+ const status = text(licenseKey['status']);
205
+ if (status === 'revoked' || this.expiryPassed(expiresAt) || this.keyGone(body)) {
206
+ const reason = detail ?? 'The licence has expired.';
207
+ return this.outcome('expired', reason, key, expiresAt, activationId);
208
+ }
209
+ /* 3. Erfolg. */
210
+ if (answer.httpStatus === 200 && status === 'granted') {
211
+ return this.outcome('valid', 'The licence key is valid.', key, expiresAt, activationId);
212
+ }
213
+ /* 4. Alles Uebrige. */
214
+ let reason = detail ?? text(body['error']) ?? 'The service did not confirm the licence key (HTTP ' + String(answer.httpStatus) + ').';
215
+ if (answer.httpStatus === 403 || /activation limit/i.test(reason)) {
216
+ /* Der Wortlaut des Dienstes endet ohne Punkt (Sandbox 23.09.2026). */
217
+ reason =
218
+ sentence(reason) +
219
+ ' Release the licence on the machine that uses it (run the server with --deactivate there) or ask LayoutBridge support (' + SUPPORT_EMAIL + ') to free the activation.';
220
+ }
221
+ else if (answer.httpStatus === 200 && status !== null && status !== 'granted') {
222
+ reason = 'The service confirmed the key but reports the state "' + status + '".';
223
+ }
224
+ return this.outcome('invalid', reason, key, expiresAt, activationId);
225
+ }
226
+ /**
227
+ * activate liefert die Aktivierung als `id` im Rumpf, validate als
228
+ * `activation.id`; sonst bleibt die gespeicherte Kennung stehen.
229
+ */
230
+ activationId(answer, storedActivationId) {
231
+ const body = answer.body;
232
+ if (body === null)
233
+ return storedActivationId;
234
+ if (answer.endpoint === 'activate')
235
+ return text(body['id']) ?? storedActivationId;
236
+ const activation = record(body['activation']);
237
+ const fromActivation = activation === null ? null : text(activation['id']);
238
+ return fromActivation ?? storedActivationId;
239
+ }
240
+ outcome(status, message, key, expiresAt, activationId) {
241
+ return {
242
+ status,
243
+ message: redactKey(message, key),
244
+ expires_at: expiresAt,
245
+ instance_id: activationId,
246
+ };
247
+ }
248
+ expiryPassed(expiresAt) {
249
+ if (expiresAt === null)
250
+ return false;
251
+ const ms = Date.parse(expiresAt);
252
+ if (Number.isNaN(ms))
253
+ return false;
254
+ return ms <= this.now().getTime();
255
+ }
256
+ /**
257
+ * null = in Ordnung; sonst der Klartext der Abweichung. Verglichen wird
258
+ * nur, was hinterlegt ist und was die Antwort auch nennt. Mit
259
+ * requireBenefit gilt eine Antwort ohne benefit_id nur dann als fremd,
260
+ * wenn sie einen Schluessel beschreibt (HTTP 200); eine Fehlerantwort
261
+ * (unbekannter Schluessel, Aktivierungslimit) bleibt "invalid" mit dem
262
+ * Wortlaut des Dienstes – gueltig wird sie ohnehin nie (E-109, Nachtrag
263
+ * 01.11.2026).
264
+ */
265
+ productMismatch(licenseKey, httpStatus) {
266
+ const organizationId = text(licenseKey['organization_id']);
267
+ const benefitId = text(licenseKey['benefit_id']);
268
+ if (this.requireBenefit && this.ids.benefit_id === null) {
269
+ return 'The licence key belongs to another product.';
270
+ }
271
+ if (this.requireBenefit && benefitId === null && httpStatus === 200) {
272
+ return 'The licence key belongs to another product.';
273
+ }
274
+ if (!polarIdsConfigured(this.ids))
275
+ return null;
276
+ if (this.ids.organization_id !== null && organizationId !== null && organizationId.toLowerCase() !== this.ids.organization_id) {
277
+ return 'The licence key belongs to another organisation.';
278
+ }
279
+ if (this.ids.benefit_id !== null && benefitId !== null && benefitId.toLowerCase() !== this.ids.benefit_id) {
280
+ return 'The licence key belongs to another product.';
281
+ }
282
+ return null;
283
+ }
284
+ }
285
+ //# sourceMappingURL=polar-verifier.js.map
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Hinterlegte Produktkennungen des Zahlungsdienstes (A-6 Ergaenzung
3
+ * 06.09.2026, Nr. 4).
4
+ *
5
+ * Der Dienst kennt keine Versionsangabe. Es gibt genau ein Produkt mit den
6
+ * Varianten Monatsabo und Jahresabo; der Connector haelt store_id,
7
+ * product_id und die zulaessigen variant_id-Werte fest und vergleicht sie
8
+ * mit dem meta-Block der Antwort. product_version aus dem Kontext wird
9
+ * nicht gegen die Antwort geprueft, weil der Dienst dazu nichts liefert.
10
+ *
11
+ * Solange kein Store besteht, sind die Konstanten leer (null bzw. leere
12
+ * Liste). Fuer die Live-Probe lassen sie sich von aussen setzen – ueber
13
+ * Umgebungsvariablen und ausschliesslich in der Bauphase, nie ueber
14
+ * config.json: config.json ist Kundenkonfiguration und koennte damit den
15
+ * Produktabgleich aushebeln.
16
+ */
17
+ import { envName, PRODUCT } from '../product.js';
18
+ /** Kennungen des Live-Stores. Traegt das Vermarktungsprojekt nach. */
19
+ export const PRODUCT_IDS = {
20
+ store_id: null,
21
+ product_id: null,
22
+ variant_ids: [],
23
+ };
24
+ function numberFromEnv(value) {
25
+ if (value === undefined)
26
+ return null;
27
+ const trimmed = value.trim();
28
+ if (trimmed.length === 0)
29
+ return null;
30
+ const parsed = Number(trimmed);
31
+ return Number.isFinite(parsed) ? parsed : null;
32
+ }
33
+ function numberListFromEnv(value) {
34
+ if (value === undefined)
35
+ return null;
36
+ const parts = value
37
+ .split(',')
38
+ .map((part) => part.trim())
39
+ .filter((part) => part.length > 0);
40
+ if (parts.length === 0)
41
+ return null;
42
+ const numbers = [];
43
+ for (const part of parts) {
44
+ const parsed = Number(part);
45
+ if (!Number.isFinite(parsed))
46
+ return null;
47
+ numbers.push(parsed);
48
+ }
49
+ return numbers;
50
+ }
51
+ /**
52
+ * Kennungen fuer diesen Lauf: die hinterlegten Konstanten, einzeln
53
+ * ueberschreibbar durch Umgebungsvariablen der Bauphase.
54
+ */
55
+ export function resolveProductIds(env = process.env, base = PRODUCT_IDS, product = PRODUCT) {
56
+ return {
57
+ store_id: numberFromEnv(env[envName('LICENSE_STORE_ID', product)]) ?? base.store_id,
58
+ product_id: numberFromEnv(env[envName('LICENSE_PRODUCT_ID', product)]) ?? base.product_id,
59
+ variant_ids: numberListFromEnv(env[envName('LICENSE_VARIANT_IDS', product)]) ?? base.variant_ids,
60
+ };
61
+ }
62
+ /** Ist ueberhaupt eine Kennung hinterlegt? */
63
+ export function productIdsConfigured(ids) {
64
+ return ids.store_id !== null || ids.product_id !== null || ids.variant_ids.length > 0;
65
+ }
66
+ /** Kurzfassung fuer Startzeile und Protokoll. */
67
+ export function describeProductIds(ids) {
68
+ if (!productIdsConfigured(ids))
69
+ return 'not set';
70
+ const variants = ids.variant_ids.length > 0 ? ids.variant_ids.join('/') : '-';
71
+ return `store ${ids.store_id ?? '-'}, product ${ids.product_id ?? '-'}, variant ${variants}`;
72
+ }
73
+ //# sourceMappingURL=product-ids.js.map
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Speicher fuer das letzte Pruefergebnis (E-60): eine kleine JSON-Datei
3
+ * neben der Serverkonfiguration. Fehlt sie oder ist sie unlesbar, gilt
4
+ * "kein Ergebnis" – nie ein Absturz.
5
+ */
6
+ import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
7
+ import { dirname } from 'node:path';
8
+ const STATUSES = ['valid', 'invalid', 'expired', 'product_mismatch', 'unreachable'];
9
+ function optionalString(value) {
10
+ return typeof value === 'string' ? value : null;
11
+ }
12
+ /** Text der Speicherdatei pruefen; alles Unbrauchbare ergibt null. */
13
+ export function parseLicenseState(text) {
14
+ let raw;
15
+ try {
16
+ raw = JSON.parse(text);
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
22
+ return null;
23
+ const record = raw;
24
+ const fp = record['key_fingerprint'];
25
+ const status = record['status'];
26
+ const checkedAt = record['checked_at'];
27
+ if (typeof fp !== 'string' || fp.length === 0)
28
+ return null;
29
+ if (typeof status !== 'string' || !STATUSES.includes(status))
30
+ return null;
31
+ if (typeof checkedAt !== 'string' || Number.isNaN(Date.parse(checkedAt)))
32
+ return null;
33
+ return {
34
+ key_fingerprint: fp,
35
+ status: status,
36
+ checked_at: checkedAt,
37
+ last_valid_at: optionalString(record['last_valid_at']),
38
+ expires_at: optionalString(record['expires_at']),
39
+ instance_id: optionalString(record['instance_id']),
40
+ /*
41
+ * A-8 (13.09.2026, E-64): das fruehere Feld customer trug die
42
+ * E-Mail-Adresse des Kunden. Es wird nicht mehr gelesen und beim
43
+ * naechsten Schreiben ueberschrieben – so verschwindet es von selbst.
44
+ */
45
+ verifier: typeof record['verifier'] === 'string' ? record['verifier'] : '',
46
+ message: typeof record['message'] === 'string' ? record['message'] : '',
47
+ };
48
+ }
49
+ export class FileLicenseStateStore {
50
+ location;
51
+ constructor(location) {
52
+ this.location = location;
53
+ }
54
+ load() {
55
+ let text;
56
+ try {
57
+ text = readFileSync(this.location, 'utf8');
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ return parseLicenseState(text);
63
+ }
64
+ save(state) {
65
+ mkdirSync(dirname(this.location), { recursive: true });
66
+ writeFileSync(this.location, JSON.stringify(state, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 });
67
+ }
68
+ clear() {
69
+ try {
70
+ unlinkSync(this.location);
71
+ }
72
+ catch {
73
+ /* schon weg */
74
+ }
75
+ }
76
+ }
77
+ /** Speicher im Arbeitsspeicher – fuer Tests. */
78
+ export class MemoryLicenseStateStore {
79
+ location = 'memory';
80
+ state = null;
81
+ load() {
82
+ return this.state === null ? null : { ...this.state };
83
+ }
84
+ save(state) {
85
+ this.state = { ...state };
86
+ }
87
+ clear() {
88
+ this.state = null;
89
+ }
90
+ }
91
+ //# sourceMappingURL=state-store.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Lizenzschranke (Auftrag A-6 mit Nachtrag 05.09.2026, E-60): Typen.
3
+ *
4
+ * Diese Schicht kennt weder InDesign noch den Transport. Sie kennt nur den
5
+ * Schluessel, ein gespeichertes Pruefergebnis und eine austauschbare
6
+ * Prueffunktion (LicenseVerifier). Der eigentliche Aufruf beim
7
+ * Zahlungsdienst kommt als eigene Klasse an diese Schnittstelle, sobald der
8
+ * Dienst mit Endpunkten beauftragt ist.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=types.js.map