@flowselections/floriday-authenticatie-module 1.0.79 → 1.0.83

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 (26) hide show
  1. package/dist-lib/components/floriday/FlorydayApiLogPage.d.ts.map +1 -1
  2. package/dist-lib/components/floriday/FlorydayApiLogPage.js +29 -3
  3. package/dist-lib/components/floriday/FlorydayWebhooksCard.d.ts.map +1 -1
  4. package/dist-lib/components/floriday/FlorydayWebhooksCard.js +20 -2
  5. package/dist-lib/components/floriday/TokenHealthCard.d.ts.map +1 -1
  6. package/dist-lib/components/floriday/TokenHealthCard.js +1 -1
  7. package/dist-lib/integrations/supabase/types.d.ts +495 -2
  8. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  9. package/dist-lib/integrations/supabase/types.js +1 -0
  10. package/dist-lib/lib/floriday-sync.functions.d.ts +24 -192
  11. package/dist-lib/lib/floriday-sync.functions.d.ts.map +1 -1
  12. package/dist-lib/lib/floriday-webhooks.functions.d.ts +37 -0
  13. package/dist-lib/lib/floriday-webhooks.functions.d.ts.map +1 -1
  14. package/dist-lib/lib/floriday-webhooks.functions.js +95 -2
  15. package/dist-lib/lib/floriday.functions.d.ts +14 -1
  16. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  17. package/dist-lib/lib/floriday.functions.js +56 -28
  18. package/dist-lib/lib/gateway/customer-stickers.d.ts +12 -8
  19. package/dist-lib/lib/gateway/customer-stickers.d.ts.map +1 -1
  20. package/dist-lib/lib/gateway/customer-stickers.js +190 -37
  21. package/dist-lib/lib/gateway/webhook.d.ts +17 -2
  22. package/dist-lib/lib/gateway/webhook.d.ts.map +1 -1
  23. package/dist-lib/lib/gateway/webhook.js +81 -6
  24. package/dist-lib/lib/module-version.d.ts +1 -1
  25. package/dist-lib/lib/module-version.js +1 -1
  26. package/package.json +2 -2
@@ -9,7 +9,7 @@ import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware.js
9
9
  import { FLORYDAY_CLOCK_SUPPLY_PERMISSION_MESSAGE, FLORYDAY_CONTRACT_PERMISSION_MESSAGE, FLORYDAY_SALES_STRATEGY_PERMISSION_MESSAGE, FLORYDAY_STANDARD_SCOPE, getFlorydayScopeForPath, isClockSupplyScope, isContractScope, isSalesStrategyScope, } from "./floriday-scopes.js";
10
10
  export { validateGln } from "./floriday-gln.js";
11
11
  import { decideGln, decidePreferredWarehouse, isOwnWarehouse, warehouseLabel } from "./floriday-own-config.js";
12
- const FLORYDAY_ENDPOINTS = {
12
+ export const FLORYDAY_ENDPOINTS = {
13
13
  staging: {
14
14
  tokenUrl: "https://idm.staging.floriday.io/oauth2/ausmw6b47z1BnlHkw0h7/v1/token",
15
15
  apiBaseUrl: "https://api.staging.floriday.io",
@@ -464,11 +464,16 @@ export async function florydayFetch(supabase, pathOrEnv, pathOrInit, maybeInit =
464
464
  if (ok && flowLog?.countItems && response) {
465
465
  try {
466
466
  const json = await response.clone().json();
467
- const items = Array.isArray(json)
467
+ const all = Array.isArray(json)
468
468
  ? json
469
469
  : Array.isArray(json?.results)
470
470
  ? json.results
471
471
  : null;
472
+ // Ophaalronde: alleen items met een hoger volgnummer dan gevraagd
473
+ // tellen als wijziging (zelfde volgnummer = al verwerkt).
474
+ const items = all && typeof flowLog?.cursorFrom === "number"
475
+ ? filterNewerThan(all, flowLog.cursorFrom)
476
+ : all;
472
477
  itemCount = items ? items.length : null;
473
478
  if (items)
474
479
  syncItems = items;
@@ -533,7 +538,14 @@ export async function florydayFetch(supabase, pathOrEnv, pathOrInit, maybeInit =
533
538
  return res;
534
539
  }
535
540
  }
536
- await writeLog(res.status, res.ok, res.ok ? undefined : `Floriday ${res.status}`, res);
541
+ if (res.ok) {
542
+ await writeLog(res.status, true, undefined, res);
543
+ }
544
+ else {
545
+ // Volledige foutinhoud (status, title, detail, traceId) + exacte URL in het logboek.
546
+ const body = await res.clone().text().catch(() => "");
547
+ await writeLog(res.status, false, `Floriday ${res.status} ${method} ${url}${body ? `: ${body.slice(0, 20000)}` : " (lege body)"}`, res);
548
+ }
537
549
  return res;
538
550
  }
539
551
  // ─── Server functions exposed to the UI ─────────────────────────────────────
@@ -1333,17 +1345,26 @@ export const updateFlorydayPublishingSettings = createServerFn({ method: "POST"
1333
1345
  // /<entity>/max-sequence, daarna /<entity>/sync/{seq}. Deze helper houdt
1334
1346
  // de cursor in `floriday_sync_state` per (connection, entity).
1335
1347
  const SEQUENCE_BATCH_CAP = 1000;
1336
- // Veiligheidsmarge op de cursor.
1348
+ // Cursorregels (1.0.82)
1337
1349
  // ----------------------------------------------------------------------------
1338
- // Floriday stempelt een wijziging met een volgnummer, maar die wijziging kan
1339
- // pas even later opvraagbaar zijn via /sync/{seq}. Zetten we de cursor na een
1340
- // lege/kleine ronde hard op het op dat moment gelezen max-sequence, dan wordt
1341
- // zo'n "net te laat gepubliceerde" wijziging definitief overgeslagen. We zetten
1342
- // de cursor daarom altijd een marge terug; opnieuw ophalen is goedkoop (dezelfde
1343
- // rij wordt idempotent bijgewerkt) en de sequence-guard voorkomt terugzetten.
1344
- export const SEQUENCE_SAFETY_MARGIN = 2000;
1345
- function cursorWithMargin(target, floor) {
1346
- return Math.max(floor, target - SEQUENCE_SAFETY_MARGIN);
1350
+ // Swagger: "Your data is up to date when your given SequenceNumber is equal to
1351
+ // the received MaximumSequenceNumber." De cursor gaat daarom alleen vooruit,
1352
+ // naar het hoogste volgnummer dat echt is opgeslagen (nooit een marge terug).
1353
+ // Een later gepubliceerde wijziging krijgt altijd een hoger volgnummer en komt
1354
+ // zo vanzelf binnen. Items met een volgnummer <= de cursor zijn al verwerkt.
1355
+ /** Alleen items met een hoger volgnummer dan `cursor` (puur, testbaar). */
1356
+ export function filterNewerThan(items, cursor) {
1357
+ return items.filter((it) => {
1358
+ const s = Number(it?.sequenceNumber ?? it?.sequence_number);
1359
+ return !Number.isFinite(s) || s > cursor;
1360
+ });
1361
+ }
1362
+ /** Hoogste volgnummer in een batch, minimaal `cursor`. */
1363
+ export function highestSequence(items, cursor) {
1364
+ return items.reduce((m, item) => {
1365
+ const s = Number(item?.sequenceNumber ?? item?.sequence_number ?? 0);
1366
+ return Number.isFinite(s) && s > m ? s : m;
1367
+ }, cursor);
1347
1368
  }
1348
1369
  async function fetchMaxSequence(supabase, env, maxSequenceEndpoint, entityType, caller) {
1349
1370
  const res = await florydayFetch(supabase, env, maxSequenceEndpoint, {
@@ -1407,6 +1428,7 @@ export async function syncFloridayEntityBySequence(supabase, env, connectionId,
1407
1428
  // in dat geval expliciet op maxSeq en markeren de rij als "backfill nodig".
1408
1429
  const hasStateRow = stateRow != null;
1409
1430
  const startCursor = stateRow?.last_sequence_number ?? 0;
1431
+ let savedCursor = startCursor;
1410
1432
  try {
1411
1433
  // Debug-logging: helpt 401's onderscheid maken tussen scope-issue,
1412
1434
  // expired token en wrong env. Token zelf wordt NIET gelogd.
@@ -1452,30 +1474,36 @@ export async function syncFloridayEntityBySequence(supabase, env, connectionId,
1452
1474
  break;
1453
1475
  }
1454
1476
  safety++;
1455
- const batch = await fetchBySequence(supabase, env, opts.bySequenceEndpointBase, cursor, opts.entityType, opts.caller);
1477
+ const raw = await fetchBySequence(supabase, env, opts.bySequenceEndpointBase, cursor, opts.entityType, opts.caller);
1478
+ // Zelfde of lager volgnummer = al verwerkt: niet opnieuw doorgeven.
1479
+ const batch = filterNewerThan(raw, cursor);
1456
1480
  if (batch.length === 0) {
1457
- // Geen nieuwe wijzigingen tot het gelezen max-sequence-punt. Cursor
1458
- // doorzetten zodat we niet elke run dezelfde lege call doen, maar met
1459
- // veiligheidsmarge: een net gestempelde wijziging kan nog onderweg zijn.
1460
- cursor = cursorWithMargin(maxSeq, startCursor);
1481
+ // Niets nieuws. Cursor blijft staan (niet vooruit springen naar maxSeq:
1482
+ // een nog niet gepubliceerde wijziging mag niet worden overgeslagen).
1461
1483
  break;
1462
1484
  }
1463
- const highest = batch.reduce((m, item) => {
1464
- const s = Number(item?.sequenceNumber ?? item?.sequence_number ?? 0);
1465
- return s > m ? s : m;
1466
- }, cursor);
1485
+ const highest = highestSequence(batch, cursor);
1467
1486
  await opts.onBatch(batch, highest);
1468
1487
  totalSynced += batch.length;
1469
1488
  if (highest <= cursor)
1470
1489
  break;
1471
1490
  cursor = highest;
1472
- if (batch.length < SEQUENCE_BATCH_CAP) {
1473
- // Kleine batch ⇒ bijgewerkt tot maxSeq op moment van de eerste call,
1474
- // maar met veiligheidsmarge (zie SEQUENCE_SAFETY_MARGIN).
1475
- cursor = cursorWithMargin(Math.max(cursor, maxSeq), startCursor);
1491
+ // Na elke gelukte pagina vastleggen: mislukt een latere pagina, dan
1492
+ // blijft de cursor op het laatst opgeslagen item.
1493
+ await supabase
1494
+ .from("floriday_sync_state")
1495
+ .upsert({
1496
+ connection_id: connectionId,
1497
+ entity_type: opts.entityType,
1498
+ last_sequence_number: cursor,
1499
+ });
1500
+ savedCursor = cursor;
1501
+ // Volle pagina: in dezelfde ronde doorhalen tot maxSeq.
1502
+ if (raw.length < SEQUENCE_BATCH_CAP)
1476
1503
  break;
1477
- }
1478
1504
  }
1505
+ if (cursor < maxSeq && safety >= 1000)
1506
+ partial = true;
1479
1507
  const nowIso = new Date().toISOString();
1480
1508
  await supabase
1481
1509
  .from("floriday_sync_state")
@@ -1504,7 +1532,7 @@ export async function syncFloridayEntityBySequence(supabase, env, connectionId,
1504
1532
  .upsert({
1505
1533
  connection_id: connectionId,
1506
1534
  entity_type: opts.entityType,
1507
- last_sequence_number: startCursor,
1535
+ last_sequence_number: savedCursor,
1508
1536
  last_error: msg.slice(0, 500),
1509
1537
  });
1510
1538
  throw e;
@@ -39,18 +39,22 @@ export interface StickerUploadRequest {
39
39
  expired: boolean;
40
40
  canRespond: boolean;
41
41
  }
42
+ export declare const UPLOAD_REQUESTS_RECHECK_MS: number;
43
+ export type UploadRequestsProbe = "ok" | "no_sequence" | "unavailable" | "error";
44
+ /** Pure beslisregel op de twee HTTP-statussen (syncStatus null = niet geprobeerd). */
45
+ export declare function decideUploadRequestsProbe(maxSeqStatus: number, syncStatus: number | null): UploadRequestsProbe;
46
+ /** Pure regel: overslaan zolang de laatste "niet beschikbaar" jonger is dan 24 uur. */
47
+ export declare function shouldSkipUnavailable(state: {
48
+ status?: string;
49
+ checkedAt?: string;
50
+ } | null, now?: number): boolean;
51
+ export declare const uploadRequestsStateKey: (connectionId: string, env: FlorydayEnvironment) => string;
42
52
  /** Inhaalronde op de sequence-sync; ook gebruikt door de sticker-cron. */
43
53
  export declare function syncStickerUploadRequests(supabase: SupabaseClient, env: FlorydayEnvironment, connectionId: string, opts?: {
44
54
  maxBatches?: number;
45
55
  deadlineMs?: number;
46
- }): Promise<{
47
- synced: number;
48
- fromSequence: number;
49
- toSequence: number;
50
- durationMs: number;
51
- backfillNeeded?: boolean;
52
- partial?: boolean;
53
- }>;
56
+ force?: boolean;
57
+ }): Promise<Record<string, unknown>>;
54
58
  /** Pure regel: alleen PENDING en niet verlopen mag beantwoord worden. */
55
59
  export declare function canRespondToUploadRequest(status: string | null, expiresOn: string | null, now?: number): {
56
60
  expired: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"customer-stickers.d.ts","sourceRoot":"","sources":["../../../src/lib/gateway/customer-stickers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"customer-stickers.d.ts","sourceRoot":"","sources":["../../../src/lib/gateway/customer-stickers.ts"],"names":[],"mappings":"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"}
@@ -61,46 +61,199 @@ export async function runCustomerStickersHandled(supabase, input) {
61
61
  }
62
62
  return { ok: true, stickerIds: ids };
63
63
  }
64
+ // ---------------------------------------------------------------------------
65
+ // Beschikbaarheid van de upload-requests-endpoints.
66
+ // De swagger kent op current-max-sequence geen 404 (alleen 401/403/423/5xx),
67
+ // dus een 404 is geen bewijs dat het endpoint ontbreekt. Volgorde:
68
+ // 1. current-max-sequence 200 → normale sync.
69
+ // 2. 404 → probeer sync/0?limitResult=1. 200 → sync vanaf 0 ("geen
70
+ // volgnummer bekend"), geen fout.
71
+ // 3. beide 404 → "niet beschikbaar in deze omgeving" met foutinhoud; pas na
72
+ // 24 uur opnieuw proberen.
73
+ // ---------------------------------------------------------------------------
74
+ export const UPLOAD_REQUESTS_RECHECK_MS = 24 * 3600000;
75
+ /** Pure beslisregel op de twee HTTP-statussen (syncStatus null = niet geprobeerd). */
76
+ export function decideUploadRequestsProbe(maxSeqStatus, syncStatus) {
77
+ if (maxSeqStatus >= 200 && maxSeqStatus < 300)
78
+ return "ok";
79
+ if (maxSeqStatus !== 404)
80
+ return "error";
81
+ if (syncStatus === null)
82
+ return "error";
83
+ if (syncStatus >= 200 && syncStatus < 300)
84
+ return "no_sequence";
85
+ if (syncStatus === 404)
86
+ return "unavailable";
87
+ return "error";
88
+ }
89
+ /** Pure regel: overslaan zolang de laatste "niet beschikbaar" jonger is dan 24 uur. */
90
+ export function shouldSkipUnavailable(state, now = Date.now()) {
91
+ if (!state || state.status !== "unavailable" || !state.checkedAt)
92
+ return false;
93
+ return now - Date.parse(state.checkedAt) < UPLOAD_REQUESTS_RECHECK_MS;
94
+ }
95
+ export const uploadRequestsStateKey = (connectionId, env) => `floriday_upload_requests_availability:${env}:${connectionId}`;
96
+ async function readAvailability(supabase, key) {
97
+ const { data } = await supabase.from("module_runtime_config").select("value").eq("key", key).maybeSingle();
98
+ try {
99
+ return data?.value ? JSON.parse(data.value) : null;
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
105
+ async function writeAvailability(supabase, key, value) {
106
+ await supabase.from("module_runtime_config")
107
+ .upsert({ key, value: JSON.stringify(value), updated_at: new Date().toISOString() }, { onConflict: "key" });
108
+ }
109
+ async function describe(res, url) {
110
+ const text = await res.text().catch(() => "");
111
+ let j = null;
112
+ try {
113
+ j = JSON.parse(text);
114
+ }
115
+ catch { /* geen JSON */ }
116
+ let host = null;
117
+ try {
118
+ host = new URL(url).host;
119
+ }
120
+ catch { /* relatief */ }
121
+ return {
122
+ status: res.status,
123
+ title: j?.title ?? null,
124
+ detail: j?.detail ?? null,
125
+ traceId: j?.traceId ?? j?.traceid ?? res.headers.get("traceparent") ?? null,
126
+ url,
127
+ host,
128
+ body: text.slice(0, 4000),
129
+ };
130
+ }
64
131
  /** Inhaalronde op de sequence-sync; ook gebruikt door de sticker-cron. */
65
132
  export async function syncStickerUploadRequests(supabase, env, connectionId, opts = {}) {
66
- return syncFloridayEntityBySequence(supabase, env, connectionId, {
67
- entityType: "customer-sticker-upload-requests",
68
- maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/customer-sticker-upload-requests/current-max-sequence`,
69
- bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/customer-sticker-upload-requests/sync`,
70
- maxBatches: opts.maxBatches,
71
- deadlineMs: opts.deadlineMs,
72
- onBatch: async (items) => {
73
- const stickerIds = Array.from(new Set(items.map((i) => i?.customerStickerId).filter(Boolean).map(String)));
74
- const soByS = new Map();
75
- if (stickerIds.length) {
76
- const { data } = await supabase.from("floriday_customer_stickers_cache")
77
- .select("floriday_id, sales_order_id").eq("connection_id", connectionId).in("floriday_id", stickerIds);
78
- for (const r of (data ?? []))
79
- soByS.set(String(r.floriday_id), r.sales_order_id ?? null);
80
- }
81
- const rows = items
82
- .filter((i) => i?.customerStickerUploadRequestId)
83
- .map((i) => ({
84
- connection_id: connectionId,
85
- floriday_id: String(i.customerStickerUploadRequestId),
86
- customer_sticker_id: i.customerStickerId ?? null,
87
- // Orderkoppeling via de sticker (op id); het schema heeft geen salesOrderId op de aanvraag.
88
- sales_order_id: i.customerStickerId ? soByS.get(String(i.customerStickerId)) ?? null : null,
89
- status: i.status ?? null,
90
- expires_on: i.expiresOn ?? null,
91
- responded_on: i.respondedOn ?? null,
92
- sequence_number: Number(i.sequenceNumber ?? 0),
93
- data: i,
94
- fetched_at: new Date().toISOString(),
95
- }));
96
- if (rows.length) {
97
- const { error } = await supabase.from("floriday_customer_sticker_upload_requests_cache")
98
- .upsert(rows, { onConflict: "connection_id,floriday_id" });
99
- if (error)
100
- throw new Error(`Stickeraanvragen opslaan: ${error.message}`);
101
- }
102
- },
133
+ const key = uploadRequestsStateKey(connectionId, env);
134
+ const state = await readAvailability(supabase, key);
135
+ if (!opts.force && shouldSkipUnavailable(state)) {
136
+ return { skipped: "unavailable", since: state.since ?? state.checkedAt, nextCheckAt: new Date(Date.parse(state.checkedAt) + UPLOAD_REQUESTS_RECHECK_MS).toISOString() };
137
+ }
138
+ const { FLORYDAY_ENDPOINTS } = await import("../floriday.functions.js");
139
+ const base = FLORYDAY_ENDPOINTS[env].apiBaseUrl;
140
+ const maxPath = `/${SUPPLIERS_API_VERSION}/customer-sticker-upload-requests/current-max-sequence`;
141
+ const syncBase = `/${SUPPLIERS_API_VERSION}/customer-sticker-upload-requests/sync`;
142
+ const maxRes = await florydayFetch(supabase, env, maxPath, {
143
+ cache: "no-store",
144
+ flowLog: { module: "floriday-sync", caller: "customer-sticker-upload-requests", action: "controle:customer-sticker-upload-requests", kind: "poll-check", countItems: false },
145
+ });
146
+ let maxSeqDetail = null;
147
+ let syncDetail = null;
148
+ let probeStatus = null;
149
+ if (!maxRes.ok) {
150
+ maxSeqDetail = await describe(maxRes, base + maxPath);
151
+ if (maxRes.status === 404) {
152
+ const probeRes = await florydayFetch(supabase, env, `${syncBase}/0?limitResult=1`, {
153
+ cache: "no-store",
154
+ flowLog: { module: "floriday-sync", caller: "customer-sticker-upload-requests", action: "proef:customer-sticker-upload-requests sync/0", kind: "poll-check", countItems: false },
155
+ });
156
+ probeStatus = probeRes.status;
157
+ if (!probeRes.ok)
158
+ syncDetail = await describe(probeRes, `${base}${syncBase}/0?limitResult=1`);
159
+ }
160
+ }
161
+ const decision = decideUploadRequestsProbe(maxRes.status, probeStatus);
162
+ const nowIso = new Date().toISOString();
163
+ if (decision === "unavailable") {
164
+ const first = state?.status !== "unavailable";
165
+ await writeAvailability(supabase, key, { status: "unavailable", since: first ? nowIso : state.since, checkedAt: nowIso, maxSequence: maxSeqDetail, sync0: syncDetail });
166
+ if (first) {
167
+ const { logFlorydayCall } = await import("../floriday-api-log.server.js");
168
+ await logFlorydayCall({
169
+ direction: "inbound", environment: env, connectionId, method: "GET", path: maxPath, httpStatus: 404, ok: false,
170
+ durationMs: 0, module: "floriday-sync", caller: "customer-sticker-upload-requests",
171
+ action: "niet beschikbaar:customer-sticker-upload-requests", kind: "status",
172
+ error: `Stickeraanvragen niet beschikbaar in deze omgeving (beide routes 404). Volgende controle over 24 uur. Foutinhoud: ${JSON.stringify({ maxSequence: maxSeqDetail, sync0: syncDetail })}`,
173
+ }).catch(() => undefined);
174
+ }
175
+ return { skipped: "unavailable", maxSequence: maxSeqDetail, sync0: syncDetail };
176
+ }
177
+ if (decision === "error") {
178
+ throw new Error(`Stickeraanvragen: Floriday ${maxRes.status}${maxSeqDetail ? ` ${JSON.stringify(maxSeqDetail)}` : ""}`);
179
+ }
180
+ if (state?.status === "unavailable" || state?.status === "no_sequence" || decision === "no_sequence") {
181
+ await writeAvailability(supabase, key, { status: decision, checkedAt: nowIso, ...(decision === "no_sequence" ? { maxSequence: maxSeqDetail } : {}) });
182
+ }
183
+ const onBatch = async (items) => {
184
+ const stickerIds = Array.from(new Set(items.map((i) => i?.customerStickerId).filter(Boolean).map(String)));
185
+ const soByS = new Map();
186
+ if (stickerIds.length) {
187
+ const { data } = await supabase.from("floriday_customer_stickers_cache")
188
+ .select("floriday_id, sales_order_id").eq("connection_id", connectionId).in("floriday_id", stickerIds);
189
+ for (const r of (data ?? []))
190
+ soByS.set(String(r.floriday_id), r.sales_order_id ?? null);
191
+ }
192
+ const rows = items
193
+ .filter((i) => i?.customerStickerUploadRequestId)
194
+ .map((i) => ({
195
+ connection_id: connectionId,
196
+ floriday_id: String(i.customerStickerUploadRequestId),
197
+ customer_sticker_id: i.customerStickerId ?? null,
198
+ // Orderkoppeling via de sticker (op id); het schema heeft geen salesOrderId op de aanvraag.
199
+ sales_order_id: i.customerStickerId ? soByS.get(String(i.customerStickerId)) ?? null : null,
200
+ status: i.status ?? null,
201
+ expires_on: i.expiresOn ?? null,
202
+ responded_on: i.respondedOn ?? null,
203
+ sequence_number: Number(i.sequenceNumber ?? 0),
204
+ data: i,
205
+ fetched_at: new Date().toISOString(),
206
+ }));
207
+ if (rows.length) {
208
+ const { error } = await supabase.from("floriday_customer_sticker_upload_requests_cache")
209
+ .upsert(rows, { onConflict: "connection_id,floriday_id" });
210
+ if (error)
211
+ throw new Error(`Stickeraanvragen opslaan: ${error.message}`);
212
+ }
213
+ };
214
+ if (decision === "ok") {
215
+ return syncFloridayEntityBySequence(supabase, env, connectionId, {
216
+ entityType: "customer-sticker-upload-requests",
217
+ maxSequenceEndpoint: maxPath,
218
+ bySequenceEndpointBase: syncBase,
219
+ maxBatches: opts.maxBatches,
220
+ deadlineMs: opts.deadlineMs,
221
+ onBatch,
222
+ });
223
+ }
224
+ // no_sequence: zelf pagineren vanaf de eigen cursor (start 0), tot een lege of kleine pagina.
225
+ const { data: st } = await supabase.from("floriday_sync_state").select("last_sequence_number")
226
+ .eq("connection_id", connectionId).eq("entity_type", "customer-sticker-upload-requests").maybeSingle();
227
+ let cursor = Number(st?.last_sequence_number ?? 0);
228
+ const from = cursor;
229
+ let synced = 0;
230
+ for (let i = 0; i < (opts.maxBatches ?? 20); i++) {
231
+ if (opts.deadlineMs && Date.now() >= opts.deadlineMs)
232
+ break;
233
+ const r = await florydayFetch(supabase, env, `${syncBase}/${cursor}`, {
234
+ cache: "no-store",
235
+ flowLog: { module: "floriday-sync", caller: "customer-sticker-upload-requests", action: "ophalen:customer-sticker-upload-requests", kind: "poll-fetch", cursorFrom: cursor, countItems: true },
236
+ });
237
+ if (!r.ok)
238
+ throw new Error(`Stickeraanvragen sync/${cursor}: Floriday ${r.status}`);
239
+ const j = await r.json().catch(() => null);
240
+ const items = Array.isArray(j) ? j : Array.isArray(j?.results) ? j.results : [];
241
+ if (items.length === 0)
242
+ break;
243
+ await onBatch(items);
244
+ synced += items.length;
245
+ const highest = items.reduce((m, it) => Math.max(m, Number(it?.sequenceNumber ?? 0)), cursor);
246
+ if (highest <= cursor)
247
+ break;
248
+ cursor = highest;
249
+ if (items.length < 1000)
250
+ break;
251
+ }
252
+ await supabase.from("floriday_sync_state").upsert({
253
+ connection_id: connectionId, entity_type: "customer-sticker-upload-requests", last_sequence_number: cursor,
254
+ last_synced_at: nowIso, last_success_at: nowIso, last_items_processed: synced, last_error: null,
103
255
  });
256
+ return { synced, fromSequence: from, toSequence: cursor, mode: "no_sequence" };
104
257
  }
105
258
  /** Pure regel: alleen PENDING en niet verlopen mag beantwoord worden. */
106
259
  export function canRespondToUploadRequest(status, expiresOn, now = Date.now()) {
@@ -1,12 +1,27 @@
1
1
  import type { SupabaseClient } from "@supabase/supabase-js";
2
2
  export declare const WEBHOOK_SUBSCRIPTIONS_TABLE = "floriday_webhook_subscriptions";
3
3
  export declare function sha256HexString(s: string): Promise<string>;
4
+ /** Adres zoals de afzender het aanriep (achter een proxy via x-forwarded-host/proto). */
5
+ export declare function originalRequestUrl(request: Request): string;
4
6
  export interface WebhookResult {
5
7
  status: number;
6
8
  payload: unknown;
7
9
  }
8
- export declare function runFloridayWebhook(supabase: SupabaseClient, token: string, rawBody: string): Promise<WebhookResult>;
9
- export declare function handleFloridayWebhook(request: Request, supabase: SupabaseClient, token: string): Promise<Response>;
10
+ export interface WebhookCallContext {
11
+ /** Volledig adres waarop de melding binnenkwam (incl. token). */
12
+ callbackUrl?: string | null;
13
+ route?: string;
14
+ }
15
+ /** Token in een callback-adres maskeren: eerste 4 en laatste 4 tekens. */
16
+ export declare function maskCallbackUrl(url: string): string;
17
+ export declare const STALE_CALLBACK_PREFIX = "floriday_webhook_stale_callback:";
18
+ /**
19
+ * Onbekend token: het volledige adres bewaren in module_runtime_config
20
+ * (alleen service-role leesbaar), met teller en eerste/laatste moment.
21
+ */
22
+ export declare function recordStaleCallback(supabase: SupabaseClient, url: string, route: string): Promise<string>;
23
+ export declare function runFloridayWebhook(supabase: SupabaseClient, token: string, rawBody: string, ctx?: WebhookCallContext): Promise<WebhookResult>;
24
+ export declare function handleFloridayWebhook(request: Request, supabase: SupabaseClient, token: string, route?: string): Promise<Response>;
10
25
  /**
11
26
  * Shell-geschikte variant zonder padparameter: POST
12
27
  * /api/public/gateway/webhook-in?t=<token>. Zelfde verificatie (token-hash,
@@ -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;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"}
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,yFAAyF;AACzF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAO3D;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKnD;AAED,eAAO,MAAM,qBAAqB,qCAAqC,CAAC;AAExE;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoB/G;AAED,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,GAAG,GAAE,kBAAuB,GAC3B,OAAO,CAAC,aAAa,CAAC,CA6KxB;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,MAAM,EACb,KAAK,SAAoB,GACxB,OAAO,CAAC,QAAQ,CAAC,CAcnB;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"}
@@ -53,9 +53,64 @@ async function logRejected(supabase, reason, connectionId, payload) {
53
53
  /* loggen mag de respons niet breken */
54
54
  }
55
55
  }
56
- export async function runFloridayWebhook(supabase, token, rawBody) {
57
- if (!token || token.length < 32)
56
+ /** Adres zoals de afzender het aanriep (achter een proxy via x-forwarded-host/proto). */
57
+ export function originalRequestUrl(request) {
58
+ const u = new URL(request.url);
59
+ const host = request.headers.get("x-forwarded-host")?.split(",")[0]?.trim();
60
+ const proto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
61
+ if (host)
62
+ u.host = host;
63
+ if (proto === "https" || proto === "http")
64
+ u.protocol = proto + ":";
65
+ return u.toString();
66
+ }
67
+ /** Token in een callback-adres maskeren: eerste 4 en laatste 4 tekens. */
68
+ export function maskCallbackUrl(url) {
69
+ const mask = (t) => (t.length <= 8 ? "****" : `${t.slice(0, 4)}…${t.slice(-4)}`);
70
+ return url
71
+ .replace(/([?&]t=)([^&#]+)/, (_m, a, t) => a + mask(t))
72
+ .replace(/(\/webhook\/)([^/?#]+)/, (_m, a, t) => a + mask(t));
73
+ }
74
+ export const STALE_CALLBACK_PREFIX = "floriday_webhook_stale_callback:";
75
+ /**
76
+ * Onbekend token: het volledige adres bewaren in module_runtime_config
77
+ * (alleen service-role leesbaar), met teller en eerste/laatste moment.
78
+ */
79
+ export async function recordStaleCallback(supabase, url, route) {
80
+ const keyHash = (await sha256HexString(url)).slice(0, 32);
81
+ const key = STALE_CALLBACK_PREFIX + keyHash;
82
+ try {
83
+ const { data } = await supabase.from("module_runtime_config").select("value").eq("key", key).maybeSingle();
84
+ let prev = null;
85
+ try {
86
+ prev = data?.value ? JSON.parse(data.value) : null;
87
+ }
88
+ catch {
89
+ prev = null;
90
+ }
91
+ const now = new Date().toISOString();
92
+ const value = {
93
+ callbackUrl: url,
94
+ route,
95
+ count: (prev?.count ?? 0) + 1,
96
+ firstAt: prev?.firstAt ?? now,
97
+ lastAt: now,
98
+ unsubscribe: prev?.unsubscribe ?? null,
99
+ };
100
+ await supabase.from("module_runtime_config")
101
+ .upsert({ key, value: JSON.stringify(value), updated_at: now }, { onConflict: "key" });
102
+ }
103
+ catch { /* vastleggen mag de respons niet breken */ }
104
+ return keyHash;
105
+ }
106
+ export async function runFloridayWebhook(supabase, token, rawBody, ctx = {}) {
107
+ const route = ctx.route ?? "webhook";
108
+ if (!token || token.length < 32) {
109
+ await logRejected(supabase, "Ongeldig of ontbrekend callback-token", null, {
110
+ route, callbackUrlMasked: ctx.callbackUrl ? maskCallbackUrl(ctx.callbackUrl) : null,
111
+ });
58
112
  return { status: 404, payload: { ok: false } };
113
+ }
59
114
  const tokenHash = await sha256HexString(token);
60
115
  const { data: sub } = await supabase
61
116
  .from(WEBHOOK_SUBSCRIPTIONS_TABLE)
@@ -63,7 +118,24 @@ export async function runFloridayWebhook(supabase, token, rawBody) {
63
118
  .eq("callback_token_hash", tokenHash)
64
119
  .maybeSingle();
65
120
  if (!sub) {
66
- await logRejected(supabase, "Onbekend callback-token", null, null);
121
+ let parsed = null;
122
+ try {
123
+ parsed = rawBody ? JSON.parse(rawBody) : null;
124
+ }
125
+ catch {
126
+ parsed = null;
127
+ }
128
+ const callbackKey = ctx.callbackUrl ? await recordStaleCallback(supabase, ctx.callbackUrl, route) : null;
129
+ await logRejected(supabase, "Onbekend callback-token", null, {
130
+ route,
131
+ reason: "Onbekend callback-token",
132
+ callbackUrlMasked: ctx.callbackUrl ? maskCallbackUrl(ctx.callbackUrl) : null,
133
+ callbackKey,
134
+ messageType: pick(parsed, "Type") ?? null,
135
+ topicArn: pick(parsed, "TopicArn") ?? null,
136
+ aggregateType: pick(parsed, "aggregateType", "AggregateType") ?? null,
137
+ eventType: pick(parsed, "eventType", "EventType") ?? null,
138
+ });
67
139
  return { status: 404, payload: { ok: false } };
68
140
  }
69
141
  let body;
@@ -196,14 +268,17 @@ export async function runFloridayWebhook(supabase, token, rawBody) {
196
268
  // Altijd 200 na vastleggen.
197
269
  return { status: 200, payload: { ok: true, ...summary } };
198
270
  }
199
- export async function handleFloridayWebhook(request, supabase, token) {
271
+ export async function handleFloridayWebhook(request, supabase, token, route = "webhook/<token>") {
200
272
  if (request.method !== "POST")
201
273
  return new Response("Method Not Allowed", { status: 405 });
202
274
  try {
203
275
  const raw = await request.text();
204
276
  if (raw.length > 256000)
205
277
  return new Response("Payload Too Large", { status: 413 });
206
- const { status, payload } = await runFloridayWebhook(supabase, token, raw);
278
+ const { status, payload } = await runFloridayWebhook(supabase, token, raw, {
279
+ callbackUrl: originalRequestUrl(request),
280
+ route,
281
+ });
207
282
  return Response.json(payload, { status });
208
283
  }
209
284
  catch (e) {
@@ -218,7 +293,7 @@ export async function handleFloridayWebhook(request, supabase, token) {
218
293
  */
219
294
  export async function handleFloridayWebhookIn(request, supabase) {
220
295
  const token = new URL(request.url).searchParams.get("t") ?? "";
221
- return handleFloridayWebhook(request, supabase, token);
296
+ return handleFloridayWebhook(request, supabase, token, "webhook-in");
222
297
  }
223
298
  // ---------------------------------------------------------------------------
224
299
  // Herhaalronde voor mislukte events (cron, elke minuut).
@@ -1,2 +1,2 @@
1
- export declare const MODULE_VERSION = "1.0.79";
1
+ export declare const MODULE_VERSION = "1.0.83";
2
2
  //# sourceMappingURL=module-version.d.ts.map
@@ -1,2 +1,2 @@
1
1
  // GEGENEREERD door scripts/write-module-version.mjs uit package.json — niet handmatig wijzigen.
2
- export const MODULE_VERSION = "1.0.79";
2
+ export const MODULE_VERSION = "1.0.83";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowselections/floriday-authenticatie-module",
3
- "version": "1.0.79",
3
+ "version": "1.0.83",
4
4
  "sideEffects": false,
5
5
  "type": "module",
6
6
  "main": "./dist-lib/index.js",
@@ -68,7 +68,7 @@
68
68
  "@cloudflare/vite-plugin": "^1.25.5",
69
69
  "@rsbuild/core": "^1.0.0",
70
70
  "@tailwindcss/vite": "^4.2.1",
71
- "@tanstack/router-plugin": "^1.167.10",
71
+ "@tanstack/router-plugin": "1.167.33",
72
72
  "tailwindcss": "^4.2.1",
73
73
  "tw-animate-css": "^1.3.4",
74
74
  "vite-tsconfig-paths": "^6.0.2",