@flowselections/layout-maker 1.53.4 → 1.55.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 (41) hide show
  1. package/dist-lib/components/LivePrintstatusRegel.d.ts.map +1 -1
  2. package/dist-lib/components/LivePrintstatusRegel.js +17 -7
  3. package/dist-lib/components/PrintQueuePage.d.ts.map +1 -1
  4. package/dist-lib/components/PrintQueuePage.js +22 -6
  5. package/dist-lib/components/TestprintDialog.d.ts.map +1 -1
  6. package/dist-lib/components/TestprintDialog.js +3 -2
  7. package/dist-lib/index.d.ts +2 -1
  8. package/dist-lib/index.d.ts.map +1 -1
  9. package/dist-lib/index.js +1 -0
  10. package/dist-lib/integrations/supabase/types.d.ts +84 -0
  11. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  12. package/dist-lib/lib/foutTekst.d.ts +2 -0
  13. package/dist-lib/lib/foutTekst.d.ts.map +1 -1
  14. package/dist-lib/lib/foutTekst.js +13 -1
  15. package/dist-lib/lib/orderData.server.d.ts.map +1 -1
  16. package/dist-lib/lib/orderData.server.js +31 -3
  17. package/dist-lib/lib/pakbonHtml.d.ts +2 -0
  18. package/dist-lib/lib/pakbonHtml.d.ts.map +1 -1
  19. package/dist-lib/lib/pakbonHtml.js +23 -4
  20. package/dist-lib/lib/printApi.d.ts +20 -0
  21. package/dist-lib/lib/printApi.d.ts.map +1 -1
  22. package/dist-lib/lib/printApi.js +19 -0
  23. package/dist-lib/lib/printQueue.server.d.ts +11 -2
  24. package/dist-lib/lib/printQueue.server.d.ts.map +1 -1
  25. package/dist-lib/lib/printQueue.server.js +78 -19
  26. package/dist-lib/lib/printService.functions.d.ts +16 -0
  27. package/dist-lib/lib/printService.functions.d.ts.map +1 -1
  28. package/dist-lib/lib/printService.functions.js +7 -1
  29. package/dist-lib/lib/printnodeStatus.d.ts +2 -0
  30. package/dist-lib/lib/printnodeStatus.d.ts.map +1 -1
  31. package/dist-lib/lib/printnodeStatus.js +6 -0
  32. package/dist-lib/lib/printnodeWebhook.d.ts +9 -8
  33. package/dist-lib/lib/printnodeWebhook.d.ts.map +1 -1
  34. package/dist-lib/lib/printnodeWebhook.js +84 -58
  35. package/dist-lib/lib/printnodeWebhookBeheer.server.d.ts +35 -0
  36. package/dist-lib/lib/printnodeWebhookBeheer.server.d.ts.map +1 -1
  37. package/dist-lib/lib/printnodeWebhookBeheer.server.js +103 -2
  38. package/dist-lib/lib/printnodeWebhookRoute.d.ts +2 -0
  39. package/dist-lib/lib/printnodeWebhookRoute.d.ts.map +1 -0
  40. package/dist-lib/lib/printnodeWebhookRoute.js +5 -0
  41. package/package.json +13 -1
@@ -16,108 +16,134 @@ function gelijk(a, b) {
16
16
  verschil |= a.charCodeAt(i) ^ b.charCodeAt(i);
17
17
  return verschil === 0;
18
18
  }
19
- /**
20
- * PrintNode documenteert alleen dat het secret "in each request" wordt
21
- * meegestuurd, niet in welk veld. Daarom: is het veld nog niet bekend, dan
22
- * zoeken we het ENE kopveld waarvan de waarde (in constante tijd) gelijk is
23
- * aan het secret en leggen alleen die naam vast. Daarna telt alleen dat veld.
24
- */
25
- export function controleerSecret(request, secret, veld) {
26
- if (veld) {
27
- const w = request.headers.get(veld);
28
- return { ok: w != null && gelijk(w.trim(), secret), veld };
29
- }
30
- const treffers = [];
31
- request.headers.forEach((waarde, naam) => {
32
- if (gelijk(waarde.trim(), secret))
33
- treffers.push(naam.toLowerCase());
34
- });
35
- return treffers.length === 1 ? { ok: true, veld: treffers[0] } : { ok: false, veld: null };
19
+ /** De kop waarin PrintNode het geheim meestuurt (hulptekst webhookpagina). */
20
+ export const SECRET_KOP = "x-printnode-webhook-secret";
21
+ /** Controleert alleen de kop X-PrintNode-Webhook-Secret, in constante tijd. */
22
+ export function controleerSecret(request, secret, _veld) {
23
+ const w = request.headers.get(SECRET_KOP);
24
+ return { ok: w != null && gelijk(w.trim(), secret), veld: SECRET_KOP };
36
25
  }
37
26
  /** @deprecated gebruik controleerSecret */
38
27
  export function secretKlopt(request, secret) {
39
- return controleerSecret(request, secret, null).ok;
28
+ return controleerSecret(request, secret).ok;
29
+ }
30
+ const wacht = (ms) => new Promise((r) => setTimeout(r, ms));
31
+ /** Zoekt de opdracht op PrintNode-id; kort opnieuw als het id nog niet is opgeslagen. */
32
+ async function zoekJob(db, externId, pogingen = 3) {
33
+ for (let i = 0; i < pogingen; i++) {
34
+ const { data } = await db.from("print_jobs").select("id").eq("extern_job_id", externId).maybeSingle();
35
+ const id = data?.id;
36
+ if (id)
37
+ return id;
38
+ if (i < pogingen - 1)
39
+ await wacht(1500);
40
+ }
41
+ return null;
42
+ }
43
+ async function noteer(db, regel, bewaren) {
44
+ if (!bewaren)
45
+ return;
46
+ try {
47
+ await db.from("printnode_webhook_meldingen").insert(regel);
48
+ const { data } = await db.from("printnode_webhook_meldingen").select("id").order("id", { ascending: false }).range(19, 19);
49
+ const grens = data?.[0]?.id;
50
+ if (grens)
51
+ await db.from("printnode_webhook_meldingen").delete().lt("id", grens);
52
+ }
53
+ catch { /* vastleggen mag verwerking niet blokkeren */ }
40
54
  }
41
55
  /**
42
56
  * Afhandeling van een webhookaanroep. Het geheim komt uit de eigen
43
57
  * koppelingstabel (servicerol). `secretVoorTest` alleen voor tests.
58
+ * Elk geldig (geheim klopt) verzoek krijgt 2xx + OK-kop, ook bij onbekende
59
+ * opdrachten of een fout in één melding.
44
60
  */
45
- export async function behandelPrintNodeWebhook(request, db, secretVoorTest) {
61
+ export async function behandelPrintNodeWebhook(request, db, secretVoorTest, opties = {}) {
46
62
  let secret = secretVoorTest;
47
- let veld = null;
48
63
  const uitTabel = secretVoorTest === undefined;
49
64
  if (uitTabel) {
50
65
  try {
51
- const { data } = await db.from("printnode_webhook_koppeling").select("geheim, secret_veld, status").eq("id", 1).maybeSingle();
66
+ const { data } = await db.from("printnode_webhook_koppeling").select("geheim, status").eq("id", 1).maybeSingle();
52
67
  const k = data;
53
- if (k?.status === "aan" && k.geheim) {
68
+ if (k?.status === "aan" && k.geheim)
54
69
  secret = k.geheim;
55
- veld = k.secret_veld ?? null;
56
- }
57
70
  }
58
71
  catch { /* fail closed */ }
59
72
  }
60
- // Fail closed: zonder koppeling wordt niets verwerkt.
61
73
  if (!secret) {
62
74
  return Response.json({ ok: false, fout: "Live printstatus is niet gekoppeld." }, { status: 503 });
63
75
  }
64
- const c = controleerSecret(request, secret, veld);
65
- if (!c.ok)
76
+ if (!controleerSecret(request, secret).ok) {
77
+ await noteer(db, { http_status: 401, antwoord: { fout: "Ongeldig secret" } }, uitTabel);
66
78
  return Response.json({ ok: false, fout: "Ongeldig secret" }, { status: 401 });
79
+ }
67
80
  if (uitTabel) {
68
81
  try {
69
82
  await db.from("printnode_webhook_koppeling").update({
70
- laatst_ontvangen_op: new Date().toISOString(),
71
- ...(veld ? {} : { secret_veld: c.veld }),
83
+ laatst_ontvangen_op: new Date().toISOString(), secret_veld: SECRET_KOP,
72
84
  }).eq("id", 1);
73
85
  }
74
- catch { /* status bijhouden mag verwerking niet blokkeren */ }
86
+ catch { /* niet blokkeren */ }
75
87
  }
76
- let body;
88
+ let berichten = [];
89
+ let jsonFout = false;
77
90
  try {
78
- body = await request.json();
91
+ const body = await request.json();
92
+ berichten = Array.isArray(body) ? body : [];
79
93
  }
80
94
  catch {
81
- return Response.json({ ok: false, fout: "Ongeldige JSON" }, { status: 400 });
95
+ jsonFout = true;
82
96
  }
83
- const berichten = Array.isArray(body) ? body : [];
84
- const { pasPrintNodeStatusToe } = await import("./printQueue.server.js");
97
+ const soorten = new Set();
98
+ const ids = [];
99
+ const nietGematcht = [];
100
+ const fouten = [];
85
101
  let jobs = 0;
86
102
  let computerBericht = false;
103
+ const pogingen = opties.wachtMs === 0 ? 1 : 3;
104
+ const { pasPrintNodeStatusToe } = await import("./printQueue.server.js");
87
105
  for (const b of berichten.slice(0, 500)) {
88
- const type = String(b?.type ?? "").toLowerCase();
89
- if (type === "print job state") {
90
- const d = b.data ?? {};
91
- const externId = d["printJobId"];
92
- const state = String(d["state"] ?? "").toLowerCase();
93
- if ((typeof externId !== "number" && typeof externId !== "string") || !state)
94
- continue;
95
- const { data: job } = await db
96
- .from("print_jobs")
97
- .select("id")
98
- .eq("extern_job_id", String(externId))
99
- .maybeSingle();
100
- const id = job?.id;
101
- if (!id)
102
- continue;
103
- await pasPrintNodeStatusToe(db, id, state, String(d["message"] ?? ""));
104
- jobs++;
106
+ try {
107
+ const type = String(b?.type ?? "").toLowerCase();
108
+ if (type)
109
+ soorten.add(type);
110
+ if (type === "print job state") {
111
+ const d = b.data ?? {};
112
+ const externId = d["printJobId"];
113
+ const state = String(d["state"] ?? "").toLowerCase();
114
+ if ((typeof externId !== "number" && typeof externId !== "string") || !state)
115
+ continue;
116
+ ids.push(`${externId}:${state}`);
117
+ const id = await zoekJob(db, String(externId), pogingen);
118
+ if (!id) {
119
+ nietGematcht.push(String(externId));
120
+ continue;
121
+ }
122
+ await pasPrintNodeStatusToe(db, id, state, String(d["message"] ?? ""));
123
+ jobs++;
124
+ }
125
+ else if (type === "computer state") {
126
+ computerBericht = true;
127
+ }
105
128
  }
106
- else if (type === "computer state") {
107
- computerBericht = true;
129
+ catch (e) {
130
+ fouten.push(e instanceof Error ? e.message.slice(0, 200) : "onbekend");
108
131
  }
109
132
  }
110
- // Een computerbericht bevat geen computer-id (alleen hostname). Om op id te
111
- // matchen halen we de actuele lijst op en slaan die op per computer-id.
112
133
  if (computerBericht) {
113
134
  try {
114
135
  await ververs(db);
115
136
  }
116
137
  catch (e) {
117
- console.warn("[printnode-webhook] computerstatus verversen mislukt:", e);
138
+ fouten.push(`computerstatus: ${e instanceof Error ? e.message.slice(0, 150) : "onbekend"}`);
118
139
  }
119
140
  }
120
- return Response.json({ ok: true, jobs, computers: computerBericht }, { headers: OK_KOP });
141
+ const antwoord = { ok: true, jobs, computers: computerBericht, onbekend: nietGematcht.length, ...(jsonFout ? { waarschuwing: "Ongeldige JSON" } : {}), ...(fouten.length ? { fouten } : {}) };
142
+ await noteer(db, {
143
+ http_status: 200, soorten: [...soorten], printnode_ids: ids.slice(0, 50),
144
+ gematcht: jobs, niet_gematcht: nietGematcht.slice(0, 50), antwoord,
145
+ }, uitTabel);
146
+ return Response.json(antwoord, { headers: OK_KOP });
121
147
  }
122
148
  /** Haalt alle computers op bij PrintNode en slaat hun status op per id. */
123
149
  export async function ververs(db) {
@@ -11,6 +11,16 @@ export interface Koppeling {
11
11
  gekoppeld_op: string | null;
12
12
  laatste_poging_op: string | null;
13
13
  laatst_ontvangen_op: string | null;
14
+ bereikbaar?: string | null;
15
+ zelftest_op?: string | null;
16
+ zelftest_fout?: string | null;
17
+ pn_url?: string | null;
18
+ pn_geslaagd?: number | null;
19
+ pn_mislukt?: number | null;
20
+ pn_geslaagd_vorig?: number | null;
21
+ pn_opgehaald_op?: string | null;
22
+ pn_ontvangen?: number | null;
23
+ pn_weggegooid?: number | null;
14
24
  }
15
25
  export declare function leesKoppeling(db: Db): Promise<Koppeling | null>;
16
26
  /** Het Shell-adres zoals de Logistiek Engine het bewaart en controleert. */
@@ -31,9 +41,34 @@ export declare function koppelWebhook(db: Db, opties?: {
31
41
  * hem bij als het Shell-adres veranderd is. Na een fout eerst even wachten.
32
42
  */
33
43
  export declare function zorgVoorWebhookKoppeling(db: Db): Promise<void>;
44
+ export type ZelftestUitkomst = "ja" | "nee" | "onbekend";
45
+ /**
46
+ * Beoordeelt het antwoord van de eigen webhookroute zonder geheim.
47
+ * JSON (401/503) = route bestaat. Alleen een echte 404 = niet bereikbaar.
48
+ */
49
+ export declare function beoordeelZelftest(status: number, contentType: string | null): ZelftestUitkomst;
50
+ /** Leest counts uit GET /webhooks (successfulRequests, failedRequests, receivedEvents, droppedEvents). */
51
+ export declare function leesCounts(counts: unknown): {
52
+ geslaagd: number | null;
53
+ mislukt: number | null;
54
+ ontvangen: number | null;
55
+ weggegooid: number | null;
56
+ };
57
+ /** Zelftest naar het eigen adres + tellingen uit GET /webhooks (op webhookId). */
58
+ export declare function controleerBereikbaarheid(db: Db): Promise<void>;
59
+ /** Bepaalt wat het scherm toont: aan / uit / onbekend. */
60
+ export declare function weergaveStatus(k: Koppeling | null): "aan" | "uit" | "onbekend";
34
61
  /** Veilige status voor het scherm: nooit het geheim. */
35
62
  export declare function veiligeStatus(k: Koppeling | null): {
36
63
  aan: boolean;
64
+ weergave: "aan" | "uit" | "onbekend";
65
+ nietBereikbaar: boolean;
66
+ pnUrl: string | null;
67
+ pnGeslaagd: number | null;
68
+ pnMislukt: number | null;
69
+ pnOntvangen: number | null;
70
+ pnWeggegooid: number | null;
71
+ pnOpgehaaldOp: string | null;
37
72
  url: string | null;
38
73
  fout: string | null;
39
74
  melding: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"printnodeWebhookBeheer.server.d.ts","sourceRoot":"","sources":["../../src/lib/printnodeWebhookBeheer.server.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,KAAK,EAAE,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAQxC,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAID,wBAAsB,aAAa,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAGrE;AAMD,4EAA4E;AAC5E,wBAAsB,cAAc,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAInE;AAQD,MAAM,WAAW,cAAc;IAAG,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE;AAEnG,0DAA0D;AAC1D,wBAAsB,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CA4CxI;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBpE;AAED,wDAAwD;AACxD,wBAAgB,aAAa,CAAC,CAAC,EAAE,SAAS,GAAG,IAAI;;;;;;;;EAUhD"}
1
+ {"version":3,"file":"printnodeWebhookBeheer.server.d.ts","sourceRoot":"","sources":["../../src/lib/printnodeWebhookBeheer.server.ts"],"names":[],"mappings":"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"}
@@ -68,6 +68,7 @@ export async function koppelWebhook(db, opties = {}) {
68
68
  geheim, secret_veld: null, webhook_id: webhookId, url: oordeel.url, status: "aan",
69
69
  fout: null, melding: opties.melding ?? null, gekoppeld_op: nu, laatste_poging_op: nu,
70
70
  });
71
+ await controleerBereikbaarheid(db);
71
72
  return { ok: true, url: oordeel.url, bijgewerkt: !!bestaand };
72
73
  }
73
74
  catch (e) {
@@ -92,8 +93,12 @@ export async function zorgVoorWebhookKoppeling(db) {
92
93
  return;
93
94
  }
94
95
  const gekoppeld = huidig?.status === "aan" && huidig.webhook_id != null && !!huidig.geheim;
95
- if (gekoppeld && huidig.url === oordeel.url)
96
+ if (gekoppeld && huidig.url === oordeel.url) {
97
+ const oud = huidig.zelftest_op ? Date.now() - new Date(huidig.zelftest_op).getTime() : Infinity;
98
+ if (huidig.bereikbaar !== "ja" || oud > CONTROLE_INTERVAL_MIN * 60000)
99
+ await controleerBereikbaarheid(db);
96
100
  return;
101
+ }
97
102
  if (huidig?.fout && huidig.laatste_poging_op &&
98
103
  Date.now() - new Date(huidig.laatste_poging_op).getTime() < WACHT_NA_FOUT_MIN * 60000)
99
104
  return;
@@ -102,10 +107,106 @@ export async function zorgVoorWebhookKoppeling(db) {
102
107
  melding: gewijzigd ? "Shell-adres gewijzigd, de printstatus wordt opnieuw gekoppeld." : null,
103
108
  });
104
109
  }
110
+ const CONTROLE_INTERVAL_MIN = 10;
111
+ const ZELFTEST_MS = 5000;
112
+ /**
113
+ * Beoordeelt het antwoord van de eigen webhookroute zonder geheim.
114
+ * JSON (401/503) = route bestaat. Alleen een echte 404 = niet bereikbaar.
115
+ */
116
+ export function beoordeelZelftest(status, contentType) {
117
+ const json = (contentType ?? "").toLowerCase().includes("application/json");
118
+ if (json)
119
+ return "ja";
120
+ if (status === 404)
121
+ return "nee";
122
+ return "onbekend";
123
+ }
124
+ /** Leest counts uit GET /webhooks (successfulRequests, failedRequests, receivedEvents, droppedEvents). */
125
+ export function leesCounts(counts) {
126
+ const c = (counts && typeof counts === "object" ? counts : {});
127
+ const n = (k) => (typeof c[k] === "number" ? c[k] : null);
128
+ return {
129
+ geslaagd: n("successfulRequests"),
130
+ mislukt: n("failedRequests"),
131
+ ontvangen: n("receivedEvents"),
132
+ weggegooid: n("droppedEvents"),
133
+ };
134
+ }
135
+ /** Zelftest naar het eigen adres + tellingen uit GET /webhooks (op webhookId). */
136
+ export async function controleerBereikbaarheid(db) {
137
+ const k = await leesKoppeling(db);
138
+ if (!k?.url)
139
+ return;
140
+ const nu = new Date().toISOString();
141
+ let bereikbaar = "onbekend";
142
+ let fout = null;
143
+ try {
144
+ const ctl = new AbortController();
145
+ const t = setTimeout(() => ctl.abort(), ZELFTEST_MS);
146
+ const r = await fetch(k.url, {
147
+ method: "POST",
148
+ headers: { "Content-Type": "application/json" },
149
+ body: "[]",
150
+ signal: ctl.signal,
151
+ }).finally(() => clearTimeout(t));
152
+ bereikbaar = beoordeelZelftest(r.status, r.headers.get("content-type"));
153
+ if (bereikbaar !== "ja")
154
+ fout = `Zelftest gaf status ${r.status}`;
155
+ }
156
+ catch (e) {
157
+ fout = e instanceof Error ? e.message : "netwerkfout";
158
+ }
159
+ const velden = { zelftest_op: nu, zelftest_fout: fout };
160
+ try {
161
+ if (k.webhook_id != null) {
162
+ const { printnodeVraag } = await import("./printService.server.js");
163
+ const lijst = await printnodeVraag("/webhooks");
164
+ const w = (Array.isArray(lijst) ? lijst : []).find((x) => Number(x.webhookId) === Number(k.webhook_id));
165
+ if (w) {
166
+ const c = leesCounts(w.counts);
167
+ velden.pn_url = w.url ?? null;
168
+ velden.pn_geslaagd_vorig = k.pn_geslaagd ?? null;
169
+ velden.pn_geslaagd = c.geslaagd;
170
+ velden.pn_mislukt = c.mislukt;
171
+ velden.pn_ontvangen = c.ontvangen;
172
+ velden.pn_weggegooid = c.weggegooid;
173
+ velden.pn_opgehaald_op = nu;
174
+ if (c.geslaagd != null && k.pn_geslaagd != null && c.geslaagd > k.pn_geslaagd)
175
+ bereikbaar = "ja";
176
+ }
177
+ }
178
+ }
179
+ catch { /* tellingen zijn aanvullend */ }
180
+ // Bewijs van PrintNode zelf telt zwaarder dan de zelftest.
181
+ if (k.laatst_ontvangen_op)
182
+ bereikbaar = "ja";
183
+ velden.bereikbaar = bereikbaar;
184
+ await schrijf(db, velden);
185
+ }
186
+ /** Bepaalt wat het scherm toont: aan / uit / onbekend. */
187
+ export function weergaveStatus(k) {
188
+ const gekoppeld = k?.status === "aan" && !!k?.geheim;
189
+ if (!gekoppeld)
190
+ return "uit";
191
+ if (k?.laatst_ontvangen_op || k?.bereikbaar === "ja")
192
+ return "aan";
193
+ if (k?.bereikbaar === "nee")
194
+ return "uit";
195
+ return "onbekend";
196
+ }
105
197
  /** Veilige status voor het scherm: nooit het geheim. */
106
198
  export function veiligeStatus(k) {
199
+ const weergave = weergaveStatus(k);
107
200
  return {
108
- aan: k?.status === "aan" && !!k?.geheim,
201
+ aan: weergave === "aan",
202
+ weergave,
203
+ nietBereikbaar: k?.status === "aan" && k?.bereikbaar === "nee" && !k?.laatst_ontvangen_op,
204
+ pnUrl: k?.pn_url ?? null,
205
+ pnGeslaagd: k?.pn_geslaagd ?? null,
206
+ pnMislukt: k?.pn_mislukt ?? null,
207
+ pnOntvangen: k?.pn_ontvangen ?? null,
208
+ pnWeggegooid: k?.pn_weggegooid ?? null,
209
+ pnOpgehaaldOp: k?.pn_opgehaald_op ?? null,
109
210
  url: k?.url ?? null,
110
211
  fout: k?.fout ?? null,
111
212
  melding: k?.melding ?? null,
@@ -0,0 +1,2 @@
1
+ export { behandelPrintNodeWebhook } from "./printnodeWebhook.js";
2
+ //# sourceMappingURL=printnodeWebhookRoute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"printnodeWebhookRoute.d.ts","sourceRoot":"","sources":["../../src/lib/printnodeWebhookRoute.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,5 @@
1
+ // ============================================================================
2
+ // src/lib/printnodeWebhookRoute.ts — losse ingang voor de Shell
3
+ // (@flowselections/layout-maker/webhook), net als ./print en ./preview.
4
+ // ============================================================================
5
+ export { behandelPrintNodeWebhook } from "./printnodeWebhook.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowselections/layout-maker",
3
- "version": "1.53.4",
3
+ "version": "1.55.1",
4
4
  "flowselections": {
5
5
  "moduleId": "layout-maker",
6
6
  "pages": [
@@ -66,6 +66,14 @@
66
66
  "methods": [
67
67
  "POST"
68
68
  ]
69
+ },
70
+ {
71
+ "routePath": "/api/public/print/printnode-webhook",
72
+ "import": "@flowselections/layout-maker/webhook",
73
+ "handler": "behandelPrintNodeWebhook",
74
+ "methods": [
75
+ "POST"
76
+ ]
69
77
  }
70
78
  ]
71
79
  },
@@ -87,6 +95,10 @@
87
95
  "./preview": {
88
96
  "types": "./dist-lib/lib/previewPdf.d.ts",
89
97
  "default": "./dist-lib/lib/previewPdf.js"
98
+ },
99
+ "./webhook": {
100
+ "types": "./dist-lib/lib/printnodeWebhookRoute.d.ts",
101
+ "default": "./dist-lib/lib/printnodeWebhookRoute.js"
90
102
  }
91
103
  },
92
104
  "files": [