@flowselections/floriday-authenticatie-module 1.0.56 → 1.0.62

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.
@@ -999,25 +999,48 @@ export async function resolveVerifiedFlorydayCronUrl(path) {
999
999
  * Deze functie schrijft die waarde bij elke cron-ronde opnieuw, zodat hij
1000
1000
  * meebeweegt met de omgeving en nooit handmatig gezet hoeft te worden.
1001
1001
  */
1002
+ const shellSkipLogged = new Map();
1003
+ async function reportShellHostSkip(reason) {
1004
+ console.warn(`[floriday] shell-host niet geschreven: ${reason}`);
1005
+ const now = Date.now();
1006
+ if ((shellSkipLogged.get(reason) ?? 0) > now - 60 * 60 * 1000)
1007
+ return;
1008
+ shellSkipLogged.set(reason, now);
1009
+ try {
1010
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
1011
+ const db = supabaseAdmin;
1012
+ const since = new Date(now - 60 * 60 * 1000).toISOString();
1013
+ const { data: recent } = await db.from("floriday_write_log").select("id")
1014
+ .eq("action", "shell-host-skipped").eq("error", reason).gte("created_at", since).limit(1);
1015
+ if (recent && recent.length)
1016
+ return;
1017
+ await db.from("floriday_write_log").insert({
1018
+ connection_id: null, module: "floriday-authenticatie", action: "shell-host-skipped",
1019
+ resource: "module_runtime_config", floriday_id: null, request: null, response: null,
1020
+ http_status: 0, ok: false, error: reason, idempotency_key: null, caller: "shell-host-sync",
1021
+ });
1022
+ }
1023
+ catch { /* melden is best-effort */ }
1024
+ }
1002
1025
  export async function syncShellHostRuntimeConfig() {
1003
- let base = null;
1026
+ // Geen project/env-fallback: alleen de actieve tenant-host uit Central Auth.
1027
+ let raw = null;
1004
1028
  try {
1005
- // Geen project/env-fallback: alleen de actieve tenant-host uit Central
1006
- // Auth mag deze waarden bepalen.
1007
1029
  const { getShellHostFromCentral } = await import("./central-auth.server.js");
1008
- const { normalizeShellBase } = await import("./shell-base.js");
1009
- const raw = await getShellHostFromCentral();
1010
- if (!raw)
1011
- return { base: null, changed: false, skipped: "Central Auth gaf geen shell-host" };
1012
- base = normalizeShellBase(raw);
1013
- if (!base) {
1014
- console.warn(`[floriday] shell-host uit Central Auth afgewezen (ongeldig): ${String(raw).slice(0, 120)}`);
1015
- return { base: null, changed: false, skipped: "ongeldige shell-host" };
1016
- }
1030
+ raw = await getShellHostFromCentral();
1017
1031
  }
1018
1032
  catch {
1033
+ await reportShellHostSkip("Central Auth niet bereikbaar");
1019
1034
  return { base: null, changed: false, skipped: "Central Auth niet bereikbaar" };
1020
1035
  }
1036
+ const { decideShellBaseWrites, normalizeShellBase } = await import("./shell-base.js");
1037
+ const pre = decideShellBaseWrites(raw, {}, false);
1038
+ if (!pre.ok) {
1039
+ await reportShellHostSkip(pre.reason === "ongeldige shell-host"
1040
+ ? `ongeldige shell-host uit Central Auth: ${String(raw).slice(0, 120)}` : pre.reason);
1041
+ return { base: null, changed: false, skipped: pre.reason };
1042
+ }
1043
+ const base = normalizeShellBase(raw);
1021
1044
  try {
1022
1045
  const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
1023
1046
  const db = supabaseAdmin;
@@ -1026,48 +1049,41 @@ export async function syncShellHostRuntimeConfig() {
1026
1049
  .select("key, value")
1027
1050
  .in("key", ["stock_queue_processor_base_url", "shell_base_url"]);
1028
1051
  const cur = new Map((rows ?? []).map((r) => [r.key, r.value]));
1029
- const stockSame = cur.get("stock_queue_processor_base_url") === base;
1030
- const shellSame = cur.get("shell_base_url") === base;
1031
- if (stockSame && shellSame)
1032
- return { base, changed: false, shellBaseChanged: false };
1033
- // Alleen schrijven als dit adres deze module aantoonbaar bedient.
1034
- const pingErr = await checkShellServesCronRoute(base, "/api/public/cron/ping");
1035
- if (pingErr) {
1036
- console.warn(`[floriday] shell-host ${base} niet geschreven: ${pingErr}`);
1037
- return { base, changed: false, skipped: pingErr };
1038
- }
1052
+ const current = { shell: cur.get("shell_base_url") ?? null, stock: cur.get("stock_queue_processor_base_url") ?? null };
1053
+ // Ping alleen nodig voor de verwerker-sleutel.
1054
+ let pingErr = null;
1055
+ if (current.stock !== base)
1056
+ pingErr = await checkShellServesCronRoute(base, "/api/public/cron/ping");
1057
+ const d = decideShellBaseWrites(raw, current, !pingErr);
1058
+ if (!d.ok)
1059
+ return { base: null, changed: false, skipped: d.reason };
1039
1060
  const now = new Date().toISOString();
1040
- let shellBaseChanged = false;
1041
- if (!shellSame) {
1042
- const previous = cur.get("shell_base_url") ?? null;
1043
- if (previous) {
1044
- await db.from("module_runtime_config").upsert({ key: "shell_base_url_previous", value: previous, updated_at: now }, { onConflict: "key" });
1061
+ if (d.writeShell) {
1062
+ if (d.previous) {
1063
+ await db.from("module_runtime_config").upsert({ key: "shell_base_url_previous", value: d.previous, updated_at: now }, { onConflict: "key" });
1045
1064
  }
1046
- await db.from("module_runtime_config").upsert({ key: "shell_base_url", value: base, updated_at: now }, { onConflict: "key" });
1047
- shellBaseChanged = true;
1065
+ const { error } = await db.from("module_runtime_config").upsert({ key: "shell_base_url", value: base, updated_at: now }, { onConflict: "key" });
1066
+ if (error)
1067
+ throw new Error(error.message);
1048
1068
  await db.from("floriday_write_log").insert({
1049
- connection_id: null,
1050
- module: "floriday-authenticatie",
1051
- action: "shell-host-changed",
1052
- resource: "module_runtime_config",
1053
- floriday_id: null,
1054
- request: { key: "shell_base_url", from: previous, to: base },
1055
- response: null,
1056
- http_status: 0,
1057
- ok: true,
1058
- error: `shell-host gewijzigd van ${previous ?? "(leeg)"} naar ${base}`,
1059
- idempotency_key: null,
1060
- caller: "shell-host-sync",
1069
+ connection_id: null, module: "floriday-authenticatie", action: "shell-host-changed",
1070
+ resource: "module_runtime_config", floriday_id: null,
1071
+ request: { key: "shell_base_url", from: d.previous, to: base }, response: null,
1072
+ http_status: 0, ok: true,
1073
+ error: `shell-host gewijzigd van ${d.previous ?? "(leeg)"} naar ${base}`,
1074
+ idempotency_key: null, caller: "shell-host-sync",
1061
1075
  }).then(() => undefined, () => undefined);
1062
1076
  }
1063
- if (!stockSame) {
1077
+ if (d.writeStock) {
1064
1078
  await db.from("module_runtime_config").upsert({ key: "stock_queue_processor_base_url", value: base, updated_at: now }, { onConflict: "key" });
1065
1079
  }
1066
- console.log(`[floriday] shell-host bijgewerkt naar ${base}`);
1067
- return { base, changed: !stockSame, shellBaseChanged };
1080
+ else if (pingErr) {
1081
+ await reportShellHostSkip(`stock_queue_processor_base_url niet bijgewerkt: ${pingErr}`);
1082
+ }
1083
+ return { base, changed: d.writeStock, shellBaseChanged: d.writeShell, skipped: pingErr ?? undefined };
1068
1084
  }
1069
1085
  catch (e) {
1070
- console.warn(`[floriday] shell-host synchroniseren mislukt: ${e?.message ?? e}`);
1086
+ await reportShellHostSkip(`shell-host synchroniseren mislukt: ${e?.message ?? e}`);
1071
1087
  return { base, changed: false };
1072
1088
  }
1073
1089
  }
@@ -1 +1 @@
1
- {"version":3,"file":"supply-line.d.ts","sourceRoot":"","sources":["../../../src/lib/gateway/supply-line.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAiH5D,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,aAAa,CACjC,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxD,OAAO,CAAC,gBAAgB,CAAC,CAmK3B;AAcD,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,CAsBnB"}
1
+ {"version":3,"file":"supply-line.d.ts","sourceRoot":"","sources":["../../../src/lib/gateway/supply-line.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA6G5D,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,aAAa,CACjC,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxD,OAAO,CAAC,gBAAgB,CAAC,CAqP3B;AAcD,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,CAsBnB"}
@@ -2,6 +2,7 @@
2
2
  // Spiegel van src/routes/api/public/gateway/supply-line.ts.
3
3
  import { resolveGatewayEnvironment } from "./shared.js";
4
4
  import { florydayWrite, getActiveFlorydayCredentials } from "../floriday.functions.js";
5
+ import { classifyWeek, interpretPutFailure, isoWeekAmsterdam, isRetryableStatus, planBaseSupply, toProblem, PRICE_LOCKED_MESSAGE, WEEK_IN_PAST_MESSAGE, } from "../base-supply-decision.js";
5
6
  function pickNumber(...vals) {
6
7
  for (const v of vals) {
7
8
  if (typeof v === "number" && isFinite(v))
@@ -20,13 +21,6 @@ function pickString(...vals) {
20
21
  return v.trim();
21
22
  return undefined;
22
23
  }
23
- function weekOfYear(date = new Date()) {
24
- const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
25
- d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
26
- const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
27
- const week = Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
28
- return { year: d.getUTCFullYear(), week };
29
- }
30
24
  function defaultPeriod() {
31
25
  const start = new Date();
32
26
  start.setUTCHours(0, 0, 0, 0);
@@ -136,23 +130,131 @@ export async function runSupplyLine(supabase, input) {
136
130
  if (supplyType === "CATALOG_PRICE") {
137
131
  const explicitYear = pickNumber(raw.year, raw?.tradePeriod?.year);
138
132
  const explicitWeek = pickNumber(raw.week, raw?.tradePeriod?.week);
139
- const computed = weekOfYear(pickString(raw.periodStart, raw?.supplyPeriod?.periodStart) ? new Date(pickString(raw.periodStart, raw?.supplyPeriod?.periodStart)) : new Date());
133
+ const ps = pickString(raw.periodStart, raw?.supplyPeriod?.periodStart);
134
+ const computed = isoWeekAmsterdam(ps ? new Date(ps) : new Date());
140
135
  const year = Math.trunc(explicitYear ?? computed.year);
141
136
  const week = Math.trunc(explicitWeek ?? computed.week);
142
- const baseSupplyBody = {
143
- basePricePerPiece: { value: pricePerPiece, currency },
144
- numberOfPieces: Math.max(0, Math.trunc(availableQuantity)),
137
+ const pieces = Math.max(0, Math.trunc(availableQuantity));
138
+ const baseSupplyBody = { basePricePerPiece: { value: pricePerPiece, currency }, numberOfPieces: pieces };
139
+ const now = isoWeekAmsterdam();
140
+ const weekClass = classifyWeek({ year, week }, now);
141
+ const tiPath = `/trade-items/${encodeURIComponent(tradeItemId)}/base-supply/${year}/${week}`;
142
+ const respond = (status, body) => ({
143
+ status,
144
+ payload: {
145
+ environment, endpointFamily: "weekly-base-supply", tradeItemId, supplyLineId: null, year, week,
146
+ currentWeek: now, ...body,
147
+ },
148
+ });
149
+ if (weekClass === "invalid") {
150
+ return respond(400, { ok: false, outcome: "error", quantitySent: false, priceStatus: "not_sent",
151
+ reason: "invalid_week", retryable: false, floriday: null, error: `Ongeldige ISO-week ${year}-${week}` });
152
+ }
153
+ if (weekClass === "past") {
154
+ return respond(409, { ok: false, outcome: "week_in_past", quantitySent: false, priceStatus: "not_sent",
155
+ reason: "week_in_past", retryable: false, floriday: null, error: WEEK_IN_PAST_MESSAGE });
156
+ }
157
+ const readExisting = async () => {
158
+ const { florydayFetch, SUPPLIERS_API_VERSION } = await import("../floriday.functions.js");
159
+ try {
160
+ const r = await florydayFetch(supabase, environment, `/${SUPPLIERS_API_VERSION}/trade-items/base-supply?year=${year}&week=${week}`, { method: "GET" });
161
+ if (!r.ok)
162
+ return { ok: false, status: r.status, item: null };
163
+ const list = await r.json().catch(() => []);
164
+ const item = Array.isArray(list) ? list.find((x) => x?.tradeItemId === tradeItemId) ?? null : null;
165
+ return { ok: true, status: r.status, item };
166
+ }
167
+ catch {
168
+ return { ok: false, status: 0, item: null };
169
+ }
145
170
  };
146
- const baseResult = await florydayWrite(supabase, environment, {
171
+ const patchQuantity = () => florydayWrite(supabase, environment, {
147
172
  module: "catalog",
148
- action: "upsert-weekly-base-supply",
149
- method: "PUT",
150
- path: `/trade-items/${encodeURIComponent(tradeItemId)}/base-supply/${year}/${week}`,
173
+ action: "patch-weekly-base-supply",
174
+ method: "PATCH",
175
+ path: `${tiPath}?numberOfPieces=${pieces}`,
151
176
  referenceId: tradeItemId,
152
- body: baseSupplyBody,
153
177
  caller,
154
- idempotencyKey: `weekly-base-supply:${environment}:${tradeItemId}:${year}:${week}:${pricePerPiece}:${availableQuantity}`,
178
+ idempotencyKey: `weekly-base-supply-patch:${environment}:${tradeItemId}:${year}:${week}:${pieces}`,
155
179
  });
180
+ const existing = await readExisting();
181
+ if (!existing.ok) {
182
+ return respond(isRetryableStatus(existing.status) ? 503 : existing.status || 502, {
183
+ ok: false, outcome: "error", quantitySent: false, priceStatus: "not_sent",
184
+ reason: "base_supply_read_failed", retryable: isRetryableStatus(existing.status),
185
+ floriday: { status: existing.status, domainErrorCode: null, title: null, detail: null },
186
+ error: `Weekaanbod lezen mislukt (HTTP ${existing.status})`,
187
+ });
188
+ }
189
+ let step = planBaseSupply(weekClass, existing.item, pricePerPiece);
190
+ let outcome = "quantity_updated";
191
+ let priceStatus = "not_sent";
192
+ let reason = null;
193
+ let problem = null;
194
+ let baseResult = null;
195
+ if (step.kind === "put") {
196
+ baseResult = await florydayWrite(supabase, environment, {
197
+ module: "catalog",
198
+ action: "upsert-weekly-base-supply",
199
+ method: "PUT",
200
+ path: tiPath,
201
+ referenceId: tradeItemId,
202
+ body: baseSupplyBody,
203
+ caller,
204
+ idempotencyKey: `weekly-base-supply:${environment}:${tradeItemId}:${year}:${week}:${pricePerPiece}:${pieces}`,
205
+ });
206
+ if (baseResult.ok) {
207
+ outcome = existing.item ? "price_updated" : "created";
208
+ priceStatus = "sent";
209
+ }
210
+ else {
211
+ problem = toProblem(baseResult.status, baseResult.data);
212
+ const f = interpretPutFailure(weekClass, problem);
213
+ if (f.kind === "week_in_past") {
214
+ return respond(409, { ok: false, outcome: "week_in_past", quantitySent: false, priceStatus: "not_sent",
215
+ reason: "week_in_past", retryable: false, floriday: problem, error: WEEK_IN_PAST_MESSAGE, attemptId: baseResult.attemptId });
216
+ }
217
+ if (f.kind === "price_locked_current_week") {
218
+ step = { kind: "patch", priceStatus: "price_locked_current_week" };
219
+ reason = "price_locked_current_week";
220
+ }
221
+ else if (f.kind === "recheck") {
222
+ const again = await readExisting();
223
+ if (again.ok && again.item) {
224
+ outcome = "already_exists";
225
+ reason = "already_exists";
226
+ step = planBaseSupply(weekClass, again.item, pricePerPiece).kind === "patch"
227
+ ? planBaseSupply(weekClass, again.item, pricePerPiece)
228
+ : { kind: "patch", priceStatus: weekClass === "current" ? "price_locked_current_week" : "unchanged" };
229
+ }
230
+ else {
231
+ return respond(problem.status || 500, { ok: false, outcome: "error", quantitySent: false, priceStatus: "not_sent",
232
+ reason: problem.domainErrorCode ?? `http_${problem.status}`, retryable: false, floriday: problem,
233
+ error: problem.title ?? baseResult.error, attemptId: baseResult.attemptId });
234
+ }
235
+ }
236
+ else {
237
+ return respond(f.retryable ? 503 : problem.status || 500, { ok: false, outcome: "error", quantitySent: false, priceStatus: "not_sent",
238
+ reason: problem.domainErrorCode ?? `http_${problem.status}`, retryable: f.retryable, floriday: problem,
239
+ error: problem.title ?? baseResult.error, attemptId: baseResult.attemptId });
240
+ }
241
+ }
242
+ }
243
+ if (step.kind === "patch") {
244
+ priceStatus = step.priceStatus;
245
+ if (step.priceStatus === "price_locked_current_week")
246
+ reason = reason === "already_exists" ? "already_exists" : "price_locked_current_week";
247
+ const patchResult = await patchQuantity();
248
+ if (!patchResult.ok) {
249
+ const pp = toProblem(patchResult.status, patchResult.data);
250
+ return respond(isRetryableStatus(pp.status) ? 503 : pp.status || 500, { ok: false, outcome: "error", quantitySent: false, priceStatus,
251
+ reason: pp.status === 404 ? "base_supply_not_found" : pp.domainErrorCode ?? `http_${pp.status}`,
252
+ retryable: isRetryableStatus(pp.status), floriday: pp, error: pp.title ?? patchResult.error, attemptId: patchResult.attemptId });
253
+ }
254
+ if (outcome !== "already_exists")
255
+ outcome = "quantity_updated";
256
+ baseResult = patchResult;
257
+ }
156
258
  let availabilityResult = null;
157
259
  if (raw.usesCatalogAvailability !== false) {
158
260
  availabilityResult = await florydayWrite(supabase, environment, {
@@ -166,56 +268,31 @@ export async function runSupplyLine(supabase, input) {
166
268
  });
167
269
  }
168
270
  await persistSupplyMapping(supabase, {
169
- connection_id: conn.id,
170
- user_id: userId,
171
- environment,
172
- trade_item_id: tradeItemId,
173
- supply_type: supplyType,
174
- endpoint_family: "weekly-base-supply",
175
- supply_line_id: null,
176
- year,
177
- week,
178
- price_per_piece: pricePerPiece,
179
- available_quantity: availableQuantity,
180
- currency,
181
- sales_unit: salesUnit,
182
- last_status: baseResult.status,
183
- last_error: baseResult.error ?? null,
184
- updated_at: new Date().toISOString(),
271
+ connection_id: conn.id, user_id: userId, environment, trade_item_id: tradeItemId, supply_type: supplyType,
272
+ endpoint_family: "weekly-base-supply", supply_line_id: null, year, week, price_per_piece: pricePerPiece,
273
+ available_quantity: availableQuantity, currency, sales_unit: salesUnit, last_status: baseResult.status,
274
+ last_error: reason, updated_at: new Date().toISOString(),
185
275
  });
186
276
  await logGatewayStep(supabase, {
187
- action: "supply-line-resolved",
188
- ok: baseResult.ok,
277
+ action: "supply-line-resolved", ok: true, status: baseResult.status, caller, connection_id: conn.id,
278
+ environment, tradeItemId, endpoint: tiPath, mapping: { endpoint_family: "weekly-base-supply", year, week, outcome, priceStatus, reason },
279
+ payload: baseSupplyBody, error: reason,
280
+ });
281
+ return respond(200, {
282
+ ok: !availabilityResult || availabilityResult.ok,
189
283
  status: baseResult.status,
190
- caller,
191
- connection_id: conn.id,
192
- environment,
193
- tradeItemId,
194
- oldEndpoint: "/supply-lines/{id}",
195
- endpoint: `/trade-items/${tradeItemId}/base-supply/${year}/${week}`,
196
- oldSupplyLineId: pickString(raw.supplyLineId, raw.id) ?? null,
197
- newSupplyLineId: null,
198
- mapping: { endpoint_family: "weekly-base-supply", year, week },
199
- payload: baseSupplyBody,
200
- error: baseResult.error ?? null,
284
+ outcome,
285
+ quantitySent: true,
286
+ priceStatus,
287
+ reason,
288
+ message: priceStatus === "price_locked_current_week" ? PRICE_LOCKED_MESSAGE : null,
289
+ retryable: false,
290
+ floriday: problem,
291
+ attemptId: baseResult.attemptId,
292
+ availabilityAttemptId: availabilityResult?.attemptId ?? null,
293
+ availabilitySent: availabilityResult ? availabilityResult.ok : null,
294
+ error: availabilityResult && !availabilityResult.ok ? availabilityResult.error : null,
201
295
  });
202
- return {
203
- status: baseResult.ok ? 200 : baseResult.status || 500,
204
- payload: {
205
- ok: baseResult.ok && (!availabilityResult || availabilityResult.ok),
206
- status: baseResult.status,
207
- environment,
208
- endpointFamily: "weekly-base-supply",
209
- tradeItemId,
210
- supplyLineId: null,
211
- year,
212
- week,
213
- attemptId: baseResult.attemptId,
214
- availabilityAttemptId: availabilityResult?.attemptId ?? null,
215
- error: baseResult.error ?? availabilityResult?.error,
216
- data: { baseSupply: baseResult.data, customerOfferAvailability: availabilityResult?.data ?? null },
217
- },
218
- };
219
296
  }
220
297
  // Batch-prijs blijft volgens 2026v1 via POST /supply-lines lopen.
221
298
  const period = defaultPeriod();
@@ -1 +1 @@
1
- {"version":3,"file":"webhook-subscribe.d.ts","sourceRoot":"","sources":["../../../src/lib/gateway/webhook-subscribe.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,KAAK,GAAG,GAAG,SAAS,GAAG,MAAM,CAAC;AAM9B,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,QAAQ,CAAC;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;CACtE,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAUvH;AAQD,iFAAiF;AACjF,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgBnF;AAuBD,wBAAsB,iCAAiC,CACrD,QAAQ,EAAE,cAAc,EACxB,WAAW,EAAE,GAAG,EAChB,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACrG,OAAO,CAAC,YAAY,CAAC,CA2GvB;AA6BD,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,cAAc;iBAKlC,MAAM;qBAc3C;AAED,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsBhH"}
1
+ {"version":3,"file":"webhook-subscribe.d.ts","sourceRoot":"","sources":["../../../src/lib/gateway/webhook-subscribe.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,KAAK,GAAG,GAAG,SAAS,GAAG,MAAM,CAAC;AAM9B,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,QAAQ,CAAC;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;CACtE,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAUvH;AAiBD,iFAAiF;AACjF,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgBnF;AAuBD,wBAAsB,iCAAiC,CACrD,QAAQ,EAAE,cAAc,EACxB,WAAW,EAAE,GAAG,EAChB,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACrG,OAAO,CAAC,YAAY,CAAC,CAmHvB;AA6BD,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,cAAc;iBAKlC,MAAM;qBAc3C;AAED,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsBhH"}
@@ -34,10 +34,21 @@ function confirmedBase(existing) {
34
34
  return null;
35
35
  }
36
36
  }
37
+ async function readShellBase(sb) {
38
+ try {
39
+ const { data } = await sb.from("module_runtime_config").select("value").eq("key", "shell_base_url").maybeSingle();
40
+ const { normalizeShellBase } = await import("../shell-base.js");
41
+ const v = data?.value;
42
+ return normalizeShellBase(typeof v === "string" ? v : v?.url ?? null);
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
37
48
  /** Slaagt alleen als het adres onze eigen webhookroute is (404 + {ok:false}). */
38
49
  export async function probeWebhookCallbackBase(base) {
39
50
  try {
40
- const res = await fetch(`${base}/api/public/gateway/webhook/${"0".repeat(64)}`, {
51
+ const res = await fetch(`${base}/api/public/gateway/webhook-in?t=${"0".repeat(64)}`, {
41
52
  method: "POST",
42
53
  headers: { "Content-Type": "application/json" },
43
54
  body: "{}",
@@ -82,8 +93,17 @@ export async function ensureFloridayWebhookSubscription(supabase, environment, o
82
93
  return { ok: false, action: "skipped", reason: `Geen actieve koppeling voor ${environment}` };
83
94
  const now = Date.now();
84
95
  const { data: existing } = await sb.from(TABLE).select("*").eq("connection_id", conn.id).maybeSingle();
85
- const base = opts.callbackBase ?? confirmedBase(existing);
86
- if (!opts.force && existing) {
96
+ // Voorkeur: de eigen Shell (shell_base_url), maar alleen als de probe
97
+ // bewijst dat de Shell onze webhookroute bedient. Anders verzoek-host of
98
+ // laatst bevestigd adres. Nooit een vast adres.
99
+ const shellBase = await readShellBase(sb);
100
+ const shellOk = shellBase ? (await probeWebhookCallbackBase(shellBase)) === null : false;
101
+ const base = (shellOk ? shellBase : null) ?? opts.callbackBase ?? confirmedBase(existing);
102
+ const currentOrigin = confirmedBase(existing);
103
+ const moveToShell = shellOk && !!existing && currentOrigin !== shellBase;
104
+ if (moveToShell)
105
+ console.log(`[webhook-ensure] ${environment}: callback ${currentOrigin} → ${shellBase}`);
106
+ if (!opts.force && existing && !moveToShell) {
87
107
  const decision = await shouldResubscribe(sb, existing, now);
88
108
  if (!decision.resubscribe)
89
109
  return { ok: true, action: "skipped", reason: decision.why };
@@ -107,7 +127,7 @@ export async function ensureFloridayWebhookSubscription(supabase, environment, o
107
127
  const lockUntil = new Date(now + 60000).toISOString();
108
128
  const bytes = crypto.getRandomValues(new Uint8Array(32));
109
129
  const token = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
110
- const callbackUrl = `${base}/api/public/gateway/webhook/${token}`;
130
+ const callbackUrl = `${base}/api/public/gateway/webhook-in?t=${token}`;
111
131
  const path = `/${SUPPLIERS_API_VERSION}/webhooks/subscriptions`;
112
132
  // Eerst vorige callback bij Floriday afmelden → nooit twee geldige abonnementen.
113
133
  if (existing?.callback_url) {
@@ -7,6 +7,12 @@ export interface WebhookResult {
7
7
  }
8
8
  export declare function runFloridayWebhook(supabase: SupabaseClient, token: string, rawBody: string): Promise<WebhookResult>;
9
9
  export declare function handleFloridayWebhook(request: Request, supabase: SupabaseClient, token: string): Promise<Response>;
10
+ /**
11
+ * Shell-geschikte variant zonder padparameter: POST
12
+ * /api/public/gateway/webhook-in?t=<token>. Zelfde verificatie (token-hash,
13
+ * SNS-handtekening) en dedupe als de route met het token in het pad.
14
+ */
15
+ export declare function handleFloridayWebhookIn(request: Request, supabase: SupabaseClient): Promise<Response>;
10
16
  export declare function runWebhookRetryRound(supabase: SupabaseClient): Promise<{
11
17
  picked: number;
12
18
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../../src/lib/gateway/webhook.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAW5D,eAAO,MAAM,2BAA2B,mCAAmC,CAAC;AAE5E,wBAAsB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGhE;AA4CD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC,CA2JxB;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,QAAQ,CAAC,CAWnB;AAKD,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBhG;AAED,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,CAWnB"}
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../../src/lib/gateway/webhook.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAW5D,eAAO,MAAM,2BAA2B,mCAAmC,CAAC;AAE5E,wBAAsB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGhE;AA4CD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC,CA2JxB;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,QAAQ,CAAC,CAWnB;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,CAGnB;AAKD,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBhG;AAED,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,CAWnB"}
@@ -211,6 +211,15 @@ export async function handleFloridayWebhook(request, supabase, token) {
211
211
  return Response.json({ ok: false }, { status: 500 });
212
212
  }
213
213
  }
214
+ /**
215
+ * Shell-geschikte variant zonder padparameter: POST
216
+ * /api/public/gateway/webhook-in?t=<token>. Zelfde verificatie (token-hash,
217
+ * SNS-handtekening) en dedupe als de route met het token in het pad.
218
+ */
219
+ export async function handleFloridayWebhookIn(request, supabase) {
220
+ const token = new URL(request.url).searchParams.get("t") ?? "";
221
+ return handleFloridayWebhook(request, supabase, token);
222
+ }
214
223
  // ---------------------------------------------------------------------------
215
224
  // Herhaalronde voor mislukte events (cron, elke minuut).
216
225
  // ---------------------------------------------------------------------------
@@ -1,2 +1,16 @@
1
1
  export declare function normalizeShellBase(raw: unknown): string | null;
2
+ export type ShellBaseDecision = {
3
+ ok: false;
4
+ reason: string;
5
+ } | {
6
+ ok: true;
7
+ base: string;
8
+ writeShell: boolean;
9
+ writeStock: boolean;
10
+ previous: string | null;
11
+ };
12
+ export declare function decideShellBaseWrites(raw: unknown, current: {
13
+ shell?: string | null;
14
+ stock?: string | null;
15
+ }, pingOk: boolean): ShellBaseDecision;
2
16
  //# sourceMappingURL=shell-base.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shell-base.d.ts","sourceRoot":"","sources":["../../src/lib/shell-base.ts"],"names":[],"mappings":"AAGA,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAkB9D"}
1
+ {"version":3,"file":"shell-base.d.ts","sourceRoot":"","sources":["../../src/lib/shell-base.ts"],"names":[],"mappings":"AAGA,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAkB9D;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAKlG,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EACzD,MAAM,EAAE,OAAO,GACd,iBAAiB,CAWnB"}
@@ -29,3 +29,20 @@ export function normalizeShellBase(raw) {
29
29
  return null;
30
30
  return `https://${host}`;
31
31
  }
32
+ // Beslist welke sleutels geschreven worden. shell_base_url = adres van de
33
+ // eigen Shell (alleen normalisatie nodig); stock_queue_processor_base_url
34
+ // stuurt de eigen verwerker en vereist een geslaagde eigen ping.
35
+ export function decideShellBaseWrites(raw, current, pingOk) {
36
+ if (raw == null || (typeof raw === "string" && !raw.trim()))
37
+ return { ok: false, reason: "Central Auth gaf geen shell-host" };
38
+ const base = normalizeShellBase(raw);
39
+ if (!base)
40
+ return { ok: false, reason: "ongeldige shell-host" };
41
+ return {
42
+ ok: true,
43
+ base,
44
+ writeShell: current.shell !== base,
45
+ writeStock: pingOk && current.stock !== base,
46
+ previous: current.shell ?? null,
47
+ };
48
+ }
@@ -1,5 +1,5 @@
1
1
  /** Moet gelijk zijn aan de gateway-routes in package.json (bewaakt door test). */
2
- export declare const SHELL_GATEWAY_ROUTES: readonly ["/api/public/gateway/active-environment", "/api/public/gateway/auto-process", "/api/public/gateway/cached-list", "/api/public/gateway/code-list", "/api/public/gateway/cron/scan", "/api/public/gateway/diagnostics", "/api/public/gateway/floricode-records", "/api/public/gateway/fulfillment-order/send", "/api/public/gateway/fulfillment-order/status", "/api/public/gateway/media", "/api/public/gateway/media-upload", "/api/public/gateway/pdf", "/api/public/gateway/pdf/bulk", "/api/public/gateway/pdf/bulk-merged", "/api/public/gateway/product-characteristics", "/api/public/gateway/schema", "/api/public/gateway/supply-line", "/api/public/gateway/floriday-get", "/api/public/gateway/write", "/api/public/gateway/trade-item-version"];
2
+ export declare const SHELL_GATEWAY_ROUTES: readonly ["/api/public/gateway/active-environment", "/api/public/gateway/auto-process", "/api/public/gateway/cached-list", "/api/public/gateway/code-list", "/api/public/gateway/cron/scan", "/api/public/gateway/diagnostics", "/api/public/gateway/floricode-records", "/api/public/gateway/fulfillment-order/send", "/api/public/gateway/fulfillment-order/status", "/api/public/gateway/media", "/api/public/gateway/media-upload", "/api/public/gateway/pdf", "/api/public/gateway/pdf/bulk", "/api/public/gateway/pdf/bulk-merged", "/api/public/gateway/product-characteristics", "/api/public/gateway/schema", "/api/public/gateway/supply-line", "/api/public/gateway/floriday-get", "/api/public/gateway/write", "/api/public/gateway/trade-item-version", "/api/public/gateway/webhook-in"];
3
3
  export type ShellRouteState = {
4
4
  path: string;
5
5
  present: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"shell-routes.d.ts","sourceRoot":"","sources":["../../src/lib/shell-routes.ts"],"names":[],"mappings":"AAYA,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,suBAqBvB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAyBF,0FAA0F;AAC1F,wBAAsB,gBAAgB,CAAC,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA4CvH;AAED,8FAA8F;AAC9F,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBlH"}
1
+ {"version":3,"file":"shell-routes.d.ts","sourceRoot":"","sources":["../../src/lib/shell-routes.ts"],"names":[],"mappings":"AAYA,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,wwBAsBvB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAyBF,0FAA0F;AAC1F,wBAAsB,gBAAgB,CAAC,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA4CvH;AAED,8FAA8F;AAC9F,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBlH"}
@@ -31,6 +31,7 @@ export const SHELL_GATEWAY_ROUTES = [
31
31
  "/api/public/gateway/floriday-get",
32
32
  "/api/public/gateway/write",
33
33
  "/api/public/gateway/trade-item-version",
34
+ "/api/public/gateway/webhook-in",
34
35
  ];
35
36
  const TTL_MS = 5 * 60 * 1000;
36
37
  const cache = new Map();