@flowselections/floriday-authenticatie-module 1.0.53 → 1.0.55

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 (46) hide show
  1. package/dist-lib/components/floriday/FlorydayConnectionPage.d.ts.map +1 -1
  2. package/dist-lib/components/floriday/FlorydayConnectionPage.js +2 -1
  3. package/dist-lib/components/floriday/FlorydayShellRoutesCard.d.ts +2 -0
  4. package/dist-lib/components/floriday/FlorydayShellRoutesCard.d.ts.map +1 -0
  5. package/dist-lib/components/floriday/FlorydayShellRoutesCard.js +22 -0
  6. package/dist-lib/components/settings/FlorydaySettingsCard.d.ts.map +1 -1
  7. package/dist-lib/components/settings/FlorydaySettingsCard.js +26 -48
  8. package/dist-lib/index.js +1 -1
  9. package/dist-lib/integrations/supabase/client.server.d.ts +2 -32082
  10. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  11. package/dist-lib/integrations/supabase/types.d.ts +1418 -6
  12. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  13. package/dist-lib/lib/cron/process-stock-queue.d.ts +44 -5
  14. package/dist-lib/lib/cron/process-stock-queue.d.ts.map +1 -1
  15. package/dist-lib/lib/cron/process-stock-queue.js +167 -96
  16. package/dist-lib/lib/floriday-gln.d.ts +6 -0
  17. package/dist-lib/lib/floriday-gln.d.ts.map +1 -0
  18. package/dist-lib/lib/floriday-gln.js +22 -0
  19. package/dist-lib/lib/floriday-own-config.d.ts +68 -0
  20. package/dist-lib/lib/floriday-own-config.d.ts.map +1 -0
  21. package/dist-lib/lib/floriday-own-config.js +73 -0
  22. package/dist-lib/lib/floriday-scopes.d.ts +7 -0
  23. package/dist-lib/lib/floriday-scopes.d.ts.map +1 -1
  24. package/dist-lib/lib/floriday-scopes.js +34 -0
  25. package/dist-lib/lib/floriday-sync.functions.d.ts.map +1 -1
  26. package/dist-lib/lib/floriday-sync.functions.js +12 -2
  27. package/dist-lib/lib/floriday.functions.d.ts +49 -12
  28. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  29. package/dist-lib/lib/floriday.functions.js +221 -63
  30. package/dist-lib/lib/gateway/diagnostics.d.ts.map +1 -1
  31. package/dist-lib/lib/gateway/diagnostics.js +12 -0
  32. package/dist-lib/lib/gateway/floriday-get.d.ts +55 -0
  33. package/dist-lib/lib/gateway/floriday-get.d.ts.map +1 -0
  34. package/dist-lib/lib/gateway/floriday-get.js +185 -0
  35. package/dist-lib/lib/gateway/index.d.ts +1 -0
  36. package/dist-lib/lib/gateway/index.d.ts.map +1 -1
  37. package/dist-lib/lib/gateway/index.js +1 -0
  38. package/dist-lib/lib/gateway/webhook-subscribe.d.ts.map +1 -1
  39. package/dist-lib/lib/gateway/webhook-subscribe.js +13 -1
  40. package/dist-lib/lib/shell-base.d.ts +2 -0
  41. package/dist-lib/lib/shell-base.d.ts.map +1 -0
  42. package/dist-lib/lib/shell-base.js +31 -0
  43. package/dist-lib/lib/shell-routes.d.ts +25 -0
  44. package/dist-lib/lib/shell-routes.d.ts.map +1 -0
  45. package/dist-lib/lib/shell-routes.js +127 -0
  46. package/package.json +6 -489
@@ -16,20 +16,59 @@ export declare function isoWeekParts(input?: string | Date): {
16
16
  /** Tijdelijk (netwerk/5xx/429) versus definitief (gegevensfout). */
17
17
  export declare function isPermanentError(message: string): boolean;
18
18
  export declare function isRateLimited(message: string): boolean;
19
- type PushOutcome = {
19
+ export type WarehouseSource = "per_item" | "on_item" | "preferred";
20
+ /**
21
+ * Kiest het magazijn zonder te raden. Volgorde: magazijn per artikel,
22
+ * magazijn op het artikel, voorkeursmagazijn van de koppeling. Een kandidaat
23
+ * telt alleen als het id een eigen magazijn is (op id, nooit op naam).
24
+ */
25
+ export declare function decideWarehouse(input: {
26
+ perItem?: string | null;
27
+ onItem?: string | null;
28
+ preferred?: string | null;
29
+ ownIds: Iterable<string>;
30
+ }): {
31
+ id: string;
32
+ source: WarehouseSource;
33
+ } | null;
34
+ export declare const SKIPPED_NO_PRICE_MESSAGE = "Prijs ontbreekt, nog niet verzonden";
35
+ export declare const SKIPPED_NO_WAREHOUSE_MESSAGE = "Geen voorkeursmagazijn voor deze koppeling; kies er een op de Connectie-pagina";
36
+ export type StockOutcome = {
37
+ outcome: "quantity_sent" | "skipped_no_price" | "skipped_no_warehouse" | "error";
38
+ availabilitySent: boolean;
39
+ quantitySent: boolean;
40
+ error: string | null;
41
+ permanent: boolean;
42
+ attemptId: string | null;
43
+ };
44
+ type WriteResult = {
20
45
  ok: boolean;
21
- permanent?: boolean;
46
+ status?: number;
22
47
  error?: string;
23
48
  attemptId?: string | null;
24
- quantitySkipped?: string;
25
49
  };
26
- /** Zet één wachtrijregel door naar Floriday. Gooit nooit. */
27
- export declare function pushStockRow(supabase: SupabaseClient, env: FlorydayEnvironment, row: StockQueueRow): Promise<PushOutcome>;
50
+ /**
51
+ * Bepaalt de afsluiting van een regel uit de twee Floriday-antwoorden.
52
+ * Puur (geen I/O) zodat het los te testen is.
53
+ * - quantity_sent alleen bij een 2xx op de base-supply-PATCH.
54
+ * - 404 op de PATCH (geen weekaanbod) = skipped_no_price, nooit verzonden.
55
+ * - beschikbaarheid alleen is nooit quantity_sent.
56
+ */
57
+ export declare function decideOutcome(availability: WriteResult | null, quantity: WriteResult | null): StockOutcome;
58
+ /**
59
+ * Zet één wachtrijregel door naar Floriday. Gooit nooit.
60
+ * Er wordt bewust GEEN PUT met basePricePerPiece gedaan zolang het prijsplan
61
+ * van de Voorraad-module niet is goedgekeurd: alleen een PATCH op een
62
+ * bestaand weekaanbod (numberOfPieces als queryparameter, geen body).
63
+ */
64
+ export declare function pushStockRow(supabase: SupabaseClient, env: FlorydayEnvironment, row: StockQueueRow): Promise<StockOutcome>;
28
65
  /** Eén verwerkingsronde. Retourneert een compacte samenvatting. */
29
66
  export declare function runStockQueueRound(supabase: SupabaseClient, limit?: number): Promise<{
30
67
  ok: true;
31
68
  claimed: number;
32
69
  succeeded: number;
70
+ skippedNoPrice: number;
71
+ skippedNoWarehouse: number;
33
72
  failed: number;
34
73
  rateLimited: boolean;
35
74
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"process-stock-queue.d.ts","sourceRoot":"","sources":["../../../src/lib/cron/process-stock-queue.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAC;AAIlC,KAAK,aAAa,GAAG;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,iBAAiB,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;AAEF,kCAAkC;AAClC,wBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAYlF;AAED,oEAAoE;AACpE,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAazD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEtD;AAmCD,KAAK,WAAW,GAAG;IACjB,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,6DAA6D;AAC7D,wBAAsB,YAAY,CAChC,QAAQ,EAAE,cAAc,EACxB,GAAG,EAAE,mBAAmB,EACxB,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,WAAW,CAAC,CA6FtB;AAED,mEAAmE;AACnE,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,cAAc,EACxB,KAAK,SAAa,GACjB,OAAO,CAAC;IACT,EAAE,EAAE,IAAI,CAAC;IACT,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC,CA4CD;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,CA4BnB;AAOD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAUlE"}
1
+ {"version":3,"file":"process-stock-queue.d.ts","sourceRoot":"","sources":["../../../src/lib/cron/process-stock-queue.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,8 @@
13
13
  // pakt het weer op.
14
14
  // ============================================================================
15
15
  import { florydayWrite, getActiveFlorydayEnvironment, syncShellHostRuntimeConfig, } from "../floriday.functions.js";
16
+ import { verifyGatewayRequest, GATEWAY_HEADER_TIMESTAMP } from "../floriday-gateway-hmac.js";
17
+ import { isOwnWarehouse } from "../floriday-own-config.js";
16
18
  const BATCH_SIZE = 25;
17
19
  /** ISO-weeknummer van vandaag. */
18
20
  export function isoWeekParts(input) {
@@ -48,31 +50,108 @@ export function isPermanentError(message) {
48
50
  export function isRateLimited(message) {
49
51
  return /\b429\b|rate limit|too many requests/i.test(message);
50
52
  }
53
+ /**
54
+ * Kiest het magazijn zonder te raden. Volgorde: magazijn per artikel,
55
+ * magazijn op het artikel, voorkeursmagazijn van de koppeling. Een kandidaat
56
+ * telt alleen als het id een eigen magazijn is (op id, nooit op naam).
57
+ */
58
+ export function decideWarehouse(input) {
59
+ const own = new Set(input.ownIds);
60
+ const candidates = [
61
+ [input.perItem, "per_item"],
62
+ [input.onItem, "on_item"],
63
+ [input.preferred, "preferred"],
64
+ ];
65
+ for (const [raw, source] of candidates) {
66
+ const id = typeof raw === "string" ? raw.trim() : "";
67
+ if (id && own.has(id))
68
+ return { id, source };
69
+ }
70
+ return null;
71
+ }
51
72
  async function resolveWarehouseId(supabase, connectionId, tradeItemId, cachedItem) {
52
- const { data: pref } = await supabase
53
- .from("trade_item_warehouse_preferences")
54
- .select("warehouse_id")
55
- .eq("connection_id", connectionId)
56
- .eq("trade_item_id", tradeItemId)
57
- .maybeSingle();
58
- const direct = pref?.warehouse_id ?? cachedItem?.warehouseId ?? cachedItem?.warehouse?.warehouseId;
59
- if (direct)
60
- return direct;
61
- const { data: whs } = await supabase
62
- .from("floriday_warehouses_cache")
63
- .select("floriday_id, data")
64
- .eq("connection_id", connectionId)
65
- .limit(200);
66
- const own = (whs ?? []).filter((w) => w?.data?.isDeleted !== true &&
67
- w?.data?.isExternal !== true &&
68
- w?.data?.hasAccess !== false);
69
- const chosen = own.find((w) => w?.data?.isDefault === true) ?? own[0];
70
- return chosen?.floriday_id ?? chosen?.data?.warehouseId;
73
+ const [{ data: pref }, { data: conn }, { data: whs }] = await Promise.all([
74
+ supabase
75
+ .from("trade_item_warehouse_preferences")
76
+ .select("warehouse_id")
77
+ .eq("connection_id", connectionId)
78
+ .eq("trade_item_id", tradeItemId)
79
+ .maybeSingle(),
80
+ supabase
81
+ .from("floriday_connections")
82
+ .select("preferred_warehouse_id")
83
+ .eq("id", connectionId)
84
+ .maybeSingle(),
85
+ supabase
86
+ .from("floriday_warehouses_cache")
87
+ .select("floriday_id, data")
88
+ .eq("connection_id", connectionId)
89
+ .limit(500),
90
+ ]);
91
+ const ownIds = (whs ?? [])
92
+ .filter((w) => isOwnWarehouse({
93
+ isDeleted: w?.data?.isDeleted,
94
+ isExternal: w?.data?.isExternal,
95
+ hasAccess: w?.data?.hasAccess,
96
+ }))
97
+ .map((w) => String(w?.floriday_id ?? w?.data?.warehouseId ?? ""))
98
+ .filter(Boolean);
99
+ const chosen = decideWarehouse({
100
+ perItem: pref?.warehouse_id ?? null,
101
+ onItem: cachedItem?.warehouseId ?? cachedItem?.warehouse?.warehouseId ?? null,
102
+ preferred: conn?.preferred_warehouse_id ?? null,
103
+ ownIds,
104
+ });
105
+ return chosen?.id;
106
+ }
107
+ export const SKIPPED_NO_PRICE_MESSAGE = "Prijs ontbreekt, nog niet verzonden";
108
+ export const SKIPPED_NO_WAREHOUSE_MESSAGE = "Geen voorkeursmagazijn voor deze koppeling; kies er een op de Connectie-pagina";
109
+ /**
110
+ * Bepaalt de afsluiting van een regel uit de twee Floriday-antwoorden.
111
+ * Puur (geen I/O) zodat het los te testen is.
112
+ * - quantity_sent alleen bij een 2xx op de base-supply-PATCH.
113
+ * - 404 op de PATCH (geen weekaanbod) = skipped_no_price, nooit verzonden.
114
+ * - beschikbaarheid alleen is nooit quantity_sent.
115
+ */
116
+ export function decideOutcome(availability, quantity) {
117
+ const availabilitySent = availability?.ok === true;
118
+ const attemptId = quantity?.attemptId ?? availability?.attemptId ?? null;
119
+ if (availability && !availability.ok) {
120
+ const message = availability.error ?? `HTTP ${availability.status}`;
121
+ return { outcome: "error", availabilitySent: false, quantitySent: false, error: message, permanent: isPermanentError(message), attemptId };
122
+ }
123
+ if (!quantity) {
124
+ return { outcome: "skipped_no_price", availabilitySent, quantitySent: false, error: SKIPPED_NO_PRICE_MESSAGE, permanent: false, attemptId };
125
+ }
126
+ if (quantity.ok) {
127
+ return { outcome: "quantity_sent", availabilitySent, quantitySent: true, error: null, permanent: false, attemptId };
128
+ }
129
+ if (quantity.status === 404) {
130
+ return {
131
+ outcome: "skipped_no_price",
132
+ availabilitySent,
133
+ quantitySent: false,
134
+ error: `${SKIPPED_NO_PRICE_MESSAGE} (geen weekaanbod in Floriday)`,
135
+ permanent: false,
136
+ attemptId,
137
+ };
138
+ }
139
+ const message = quantity.error ?? `HTTP ${quantity.status}`;
140
+ return { outcome: "error", availabilitySent, quantitySent: false, error: message, permanent: isPermanentError(message), attemptId };
141
+ }
142
+ function errorOutcome(message, permanent) {
143
+ return { outcome: "error", availabilitySent: false, quantitySent: false, error: message, permanent, attemptId: null };
71
144
  }
72
- /** Zet één wachtrijregel door naar Floriday. Gooit nooit. */
145
+ /**
146
+ * Zet één wachtrijregel door naar Floriday. Gooit nooit.
147
+ * Er wordt bewust GEEN PUT met basePricePerPiece gedaan zolang het prijsplan
148
+ * van de Voorraad-module niet is goedgekeurd: alleen een PATCH op een
149
+ * bestaand weekaanbod (numberOfPieces als queryparameter, geen body).
150
+ */
73
151
  export async function pushStockRow(supabase, env, row) {
74
- const pieces = Math.max(0, Number(row.number_of_pieces ?? 0) || 0);
152
+ const pieces = Math.max(0, Math.floor(Number(row.number_of_pieces ?? 0) || 0));
75
153
  const available = row.desired_available ?? pieces > 0;
154
+ const { year, week } = isoWeekParts();
76
155
  try {
77
156
  const { data: cached } = await supabase
78
157
  .from("floriday_trade_items_cache")
@@ -81,75 +160,65 @@ export async function pushStockRow(supabase, env, row) {
81
160
  .eq("floriday_id", row.trade_item_id)
82
161
  .maybeSingle();
83
162
  if (!cached) {
84
- return {
85
- ok: false,
86
- permanent: true,
87
- error: "Dit artikel staat nog niet in de lokale Floriday-cache. Synchroniseer de artikelen en probeer opnieuw.",
88
- };
163
+ return errorOutcome("Dit artikel staat nog niet in de lokale Floriday-cache. Synchroniseer de artikelen en probeer opnieuw.", true);
89
164
  }
90
165
  const item = cached.data ?? {};
91
166
  const warehouseId = await resolveWarehouseId(supabase, row.connection_id, row.trade_item_id, item);
92
- // 1. Werkelijk aantal stuks via het weekaanbod van de lopende week.
93
- const { year, week } = isoWeekParts();
94
- const { data: supply } = await supabase
95
- .from("trade_item_supply_lines")
96
- .select("price_per_piece, currency")
97
- .eq("connection_id", row.connection_id)
98
- .eq("trade_item_id", row.trade_item_id)
99
- .order("updated_at", { ascending: false })
100
- .limit(1)
101
- .maybeSingle();
102
- const pricePerPiece = Number(supply?.price_per_piece ?? NaN);
103
- let quantitySkipped;
104
- if (Number.isFinite(pricePerPiece) && pricePerPiece > 0) {
105
- const qty = await florydayWrite(supabase, env, {
106
- module: "inventory",
107
- action: "edit-weekly-base-supply",
108
- method: "PUT",
109
- path: `/trade-items/${row.trade_item_id}/base-supply/${year}/${week}`,
110
- referenceId: row.trade_item_id,
111
- idempotencyKey: `inventory:weekly-base-supply:${row.trade_item_id}:${year}:${week}:${pieces}`,
112
- body: {
113
- basePricePerPiece: {
114
- currency: supply?.currency ?? "EUR",
115
- value: pricePerPiece,
116
- },
117
- numberOfPieces: pieces,
118
- },
119
- });
120
- if (!qty.ok) {
121
- const message = qty.error ?? `HTTP ${qty.status}`;
122
- return { ok: false, permanent: isPermanentError(message), error: message };
123
- }
124
- }
125
- else {
126
- quantitySkipped =
127
- "Aantal niet doorgezet: er is nog geen prijs bekend voor dit artikel.";
167
+ if (!warehouseId) {
168
+ // Niet raden: niets naar Floriday. De Voorraad-module zet de regel
169
+ // zelf terug op pending zodra er een magazijn is.
170
+ return {
171
+ outcome: "skipped_no_warehouse",
172
+ availabilitySent: false,
173
+ quantitySent: false,
174
+ error: SKIPPED_NO_WAREHOUSE_MESSAGE,
175
+ permanent: false,
176
+ attemptId: null,
177
+ };
128
178
  }
129
- // 2. Beschikbaarheidsschakelaar.
130
- const body = { available };
131
- if (warehouseId)
132
- body.warehouseId = warehouseId;
133
- const res = await florydayWrite(supabase, env, {
179
+ // 1. Beschikbaarheid. Sleutel zonder aantal, zodat een latere echte
180
+ // aantal-verzending (eigen sleutel) nooit wordt overgeslagen.
181
+ const body = { available, warehouseId };
182
+ const availability = (await florydayWrite(supabase, env, {
134
183
  module: "inventory",
135
184
  action: "publish-continuous-stock",
136
185
  method: "PUT",
137
186
  path: `/trade-items/${row.trade_item_id}/continuous-stock`,
138
187
  referenceId: row.trade_item_id,
139
- idempotencyKey: `inventory:continuous-stock:${row.trade_item_id}:${available}:${pieces}`,
188
+ idempotencyKey: `inventory:continuous-stock:${row.trade_item_id}:${year}:${week}:${available}:${row.id}`,
140
189
  body,
141
- });
142
- if (!res.ok) {
143
- const message = res.error ?? `HTTP ${res.status}`;
144
- return { ok: false, permanent: isPermanentError(message), error: message };
145
- }
146
- return { ok: true, attemptId: res.attemptId ?? null, quantitySkipped };
190
+ }));
191
+ if (!availability.ok)
192
+ return decideOutcome(availability, null);
193
+ // 2. Aantal: alleen PATCH op bestaand weekaanbod.
194
+ const quantity = (await florydayWrite(supabase, env, {
195
+ module: "inventory",
196
+ action: "patch-weekly-base-supply",
197
+ method: "PATCH",
198
+ path: `/trade-items/${row.trade_item_id}/base-supply/${year}/${week}?numberOfPieces=${pieces}`,
199
+ referenceId: row.trade_item_id,
200
+ idempotencyKey: `inventory:patch-weekly-base-supply:${row.trade_item_id}:${year}:${week}:${pieces}:${row.id}`,
201
+ }));
202
+ return decideOutcome(availability, quantity);
147
203
  }
148
204
  catch (e) {
149
205
  const message = e instanceof Error ? e.message : String(e);
150
- return { ok: false, permanent: isPermanentError(message), error: message };
206
+ return errorOutcome(message, isPermanentError(message));
151
207
  }
152
208
  }
209
+ async function completeRow(supabase, id, o) {
210
+ const { error } = await supabase.rpc("complete_floriday_stock_queue_outcome", {
211
+ p_id: id,
212
+ p_outcome: o.outcome,
213
+ p_availability_sent: o.availabilitySent,
214
+ p_quantity_sent: o.quantitySent,
215
+ p_error: o.error,
216
+ p_attempt_id: o.attemptId,
217
+ p_permanent: o.permanent,
218
+ });
219
+ if (error)
220
+ console.error("[stock-queue] afsluiten mislukt:", id, error.message);
221
+ }
153
222
  /** Eén verwerkingsronde. Retourneert een compacte samenvatting. */
154
223
  export async function runStockQueueRound(supabase, limit = BATCH_SIZE) {
155
224
  const env = await getActiveFlorydayEnvironment(supabase);
@@ -158,48 +227,50 @@ export async function runStockQueueRound(supabase, limit = BATCH_SIZE) {
158
227
  throw new Error(error.message);
159
228
  const rows = (claimed ?? []);
160
229
  let succeeded = 0;
230
+ let skippedNoPrice = 0;
231
+ let skippedNoWarehouse = 0;
161
232
  let failed = 0;
162
233
  let rateLimited = false;
163
234
  for (const row of rows) {
164
235
  if (rateLimited) {
165
- // Ronde afgebroken: regel meteen vrijgeven voor de volgende tick.
166
- await supabase.rpc("complete_floriday_stock_queue", {
167
- p_id: row.id,
168
- p_ok: false,
169
- p_error: "Snelheidslimiet van Floriday bereikt; volgende ronde opnieuw.",
170
- p_permanent: false,
171
- p_attempt_id: null,
172
- });
236
+ await completeRow(supabase, row.id, errorOutcome("Snelheidslimiet van Floriday bereikt; volgende ronde opnieuw.", false));
173
237
  continue;
174
238
  }
175
239
  const outcome = await pushStockRow(supabase, env, row);
176
- if (outcome.ok)
240
+ if (outcome.outcome === "quantity_sent")
177
241
  succeeded += 1;
242
+ else if (outcome.outcome === "skipped_no_price")
243
+ skippedNoPrice += 1;
244
+ else if (outcome.outcome === "skipped_no_warehouse")
245
+ skippedNoWarehouse += 1;
178
246
  else {
179
247
  failed += 1;
180
248
  if (outcome.error && isRateLimited(outcome.error))
181
249
  rateLimited = true;
182
250
  }
183
- await supabase.rpc("complete_floriday_stock_queue", {
184
- p_id: row.id,
185
- p_ok: outcome.ok,
186
- p_error: outcome.ok ? outcome.quantitySkipped ?? null : outcome.error ?? "Onbekende fout",
187
- p_permanent: outcome.permanent ?? false,
188
- p_attempt_id: outcome.attemptId ?? null,
189
- });
251
+ await completeRow(supabase, row.id, outcome);
190
252
  }
191
- return { ok: true, claimed: rows.length, succeeded, failed, rateLimited };
253
+ return { ok: true, claimed: rows.length, succeeded, skippedNoPrice, skippedNoWarehouse, failed, rateLimited };
192
254
  }
193
255
  /**
194
256
  * Cron-handler, zelfde vorm en beveiliging als de overige cron-routes:
195
257
  * `apikey`-header moet gelijk zijn aan `SUPABASE_PUBLISHABLE_KEY`.
196
258
  */
197
259
  export async function handleProcessStockQueue(request, supabase) {
198
- const apikey = request.headers.get("apikey") ?? request.headers.get("x-api-key");
199
- const expected = process.env.SUPABASE_PUBLISHABLE_KEY;
200
- if (!expected || !apikey || apikey !== expected) {
201
- return new Response("Unauthorized", { status: 401 });
260
+ // Voorkeur: gedeelde HMAC-handtekening. Terugval (tijdelijk, tot de
261
+ // herinplanning in een nieuwe Shell): de bestaande apikey-header.
262
+ let authorized = false;
263
+ if (request.headers.get(GATEWAY_HEADER_TIMESTAMP)) {
264
+ const auth = await verifyGatewayRequest(request.clone());
265
+ authorized = auth.ok;
202
266
  }
267
+ if (!authorized) {
268
+ const apikey = request.headers.get("apikey") ?? request.headers.get("x-api-key");
269
+ const expected = process.env.SUPABASE_PUBLISHABLE_KEY;
270
+ authorized = !!expected && !!apikey && apikey === expected;
271
+ }
272
+ if (!authorized)
273
+ return new Response("Unauthorized", { status: 401 });
203
274
  // Zelfherstel van de shell-host (fire-and-forget): draait elke minuut mee,
204
275
  // zodat een nieuwe of verhuisde shell binnen één cron-tick de juiste
205
276
  // callback-URL in `module_runtime_config` heeft staan. De functie cachet
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Valideert een GS1 GLN: exact 13 cijfers met geldige check digit.
3
+ * Retourneert null als geldig, anders een nl-foutmelding.
4
+ */
5
+ export declare function validateGln(input: string): string | null;
6
+ //# sourceMappingURL=floriday-gln.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floriday-gln.d.ts","sourceRoot":"","sources":["../../src/lib/floriday-gln.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAaxD"}
@@ -0,0 +1,22 @@
1
+ // GS1 GLN-validatie (zonder serverimports).
2
+ /**
3
+ * Valideert een GS1 GLN: exact 13 cijfers met geldige check digit.
4
+ * Retourneert null als geldig, anders een nl-foutmelding.
5
+ */
6
+ export function validateGln(input) {
7
+ const v = input.trim();
8
+ if (!v)
9
+ return "GLN-code is verplicht";
10
+ if (!/^\d{13}$/.test(v))
11
+ return "GLN-code moet uit precies 13 cijfers bestaan";
12
+ const digits = v.split("").map(Number);
13
+ // GS1 mod-10 voor GLN-13: positie 1 (meest links) ×1, positie 2 ×3, … positie 12 ×3.
14
+ let sum = 0;
15
+ for (let i = 0; i < 12; i++) {
16
+ sum += digits[i] * (i % 2 === 0 ? 1 : 3);
17
+ }
18
+ const check = (10 - (sum % 10)) % 10;
19
+ if (check !== digits[12])
20
+ return "GLN-code heeft een ongeldig controlecijfer";
21
+ return null;
22
+ }
@@ -0,0 +1,68 @@
1
+ export interface OwnWarehouseInput {
2
+ id: string;
3
+ name?: string | null;
4
+ isDeleted?: boolean | null;
5
+ isExternal?: boolean | null;
6
+ hasAccess?: boolean | null;
7
+ isDefault?: boolean | null;
8
+ }
9
+ /** Eigen magazijn: niet verwijderd, niet extern, met toegang. */
10
+ export declare function isOwnWarehouse(w: {
11
+ isDeleted?: boolean | null;
12
+ isExternal?: boolean | null;
13
+ hasAccess?: boolean | null;
14
+ }): boolean;
15
+ /**
16
+ * Leesbare naam voor een magazijn uit de cache. Lege tekst telt als
17
+ * ontbrekend. Volgorde: naam, adresregel + plaats, plaats, GLN, id.
18
+ * Alleen voor weergave; de keuze loopt altijd op id.
19
+ */
20
+ export declare function warehouseLabel(row: {
21
+ name?: unknown;
22
+ gln_code?: unknown;
23
+ floriday_id?: unknown;
24
+ data?: any;
25
+ }): string;
26
+ export type PreferredWarehouseDecision = {
27
+ kind: "keep";
28
+ id: string;
29
+ } | {
30
+ kind: "missing";
31
+ id: string;
32
+ } | {
33
+ kind: "fill";
34
+ id: string;
35
+ reason: "single" | "default";
36
+ } | {
37
+ kind: "choice_needed";
38
+ } | {
39
+ kind: "none";
40
+ };
41
+ export declare function decidePreferredWarehouse(input: {
42
+ currentId: string | null | undefined;
43
+ warehouses: OwnWarehouseInput[];
44
+ }): PreferredWarehouseDecision;
45
+ export type GlnDecision = {
46
+ kind: "keep";
47
+ gln: string;
48
+ } | {
49
+ kind: "fill";
50
+ gln: string;
51
+ } | {
52
+ kind: "mismatch";
53
+ stored: string;
54
+ fromFloriday: string;
55
+ } | {
56
+ kind: "unavailable";
57
+ stored: string | null;
58
+ } | {
59
+ kind: "invalid";
60
+ stored: string | null;
61
+ fromFloriday: string;
62
+ error: string;
63
+ };
64
+ export declare function decideGln(input: {
65
+ stored: string | null | undefined;
66
+ fromFloriday: string | null | undefined;
67
+ }): GlnDecision;
68
+ //# sourceMappingURL=floriday-own-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floriday-own-config.d.ts","sourceRoot":"","sources":["../../src/lib/floriday-own-config.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC5B;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,CAAC,EAAE;IAChC,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC5B,GAAG,OAAO,CAEV;AAQD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE;IAClC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ,GAAG,MAAM,CAcT;AAED,MAAM,MAAM,0BAA0B,GAClC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,GACzB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACrC,UAAU,EAAE,iBAAiB,EAAE,CAAC;CACjC,GAAG,0BAA0B,CAa7B;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpF,wBAAgB,SAAS,CAAC,KAAK,EAAE;IAC/B,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACzC,GAAG,WAAW,CASd"}
@@ -0,0 +1,73 @@
1
+ // ============================================================================
2
+ // Pure beslisregels voor GLN en voorkeursmagazijn van de eigen organisatie.
3
+ // Geen serverimports: bruikbaar in server, client en tests.
4
+ // Regel: alleen lege velden worden automatisch gevuld, altijd op id; een
5
+ // bestaande (gebruikers)keuze wordt nooit overschreven.
6
+ // ============================================================================
7
+ import { validateGln } from "./floriday-gln.js";
8
+ /** Eigen magazijn: niet verwijderd, niet extern, met toegang. */
9
+ export function isOwnWarehouse(w) {
10
+ return w.isDeleted !== true && w.isExternal !== true && w.hasAccess !== false;
11
+ }
12
+ function txt(v) {
13
+ if (typeof v !== "string")
14
+ return null;
15
+ const t = v.trim();
16
+ return t ? t : null;
17
+ }
18
+ /**
19
+ * Leesbare naam voor een magazijn uit de cache. Lege tekst telt als
20
+ * ontbrekend. Volgorde: naam, adresregel + plaats, plaats, GLN, id.
21
+ * Alleen voor weergave; de keuze loopt altijd op id.
22
+ */
23
+ export function warehouseLabel(row) {
24
+ const d = row?.data ?? {};
25
+ const name = txt(row?.name) ?? txt(d?.name);
26
+ if (name)
27
+ return name;
28
+ const addr = d?.location?.address ?? d?.address ?? {};
29
+ const line = txt(addr?.addressLine) ?? txt(addr?.street);
30
+ const city = txt(addr?.city) ?? txt(d?.location?.city);
31
+ if (line && city)
32
+ return `${line}, ${city}`;
33
+ if (line)
34
+ return line;
35
+ if (city)
36
+ return city;
37
+ const gln = txt(row?.gln_code) ?? txt(d?.location?.gln);
38
+ if (gln)
39
+ return `Magazijn zonder naam (GLN ${gln})`;
40
+ const id = txt(row?.floriday_id) ?? txt(d?.warehouseId);
41
+ return id ? `Magazijn zonder naam (id ${id.slice(0, 8)}…)` : "Magazijn zonder naam";
42
+ }
43
+ export function decidePreferredWarehouse(input) {
44
+ const own = input.warehouses.filter((w) => w.id && isOwnWarehouse(w));
45
+ const current = input.currentId?.trim() || null;
46
+ if (current) {
47
+ return own.some((w) => w.id === current)
48
+ ? { kind: "keep", id: current }
49
+ : { kind: "missing", id: current };
50
+ }
51
+ if (own.length === 0)
52
+ return { kind: "none" };
53
+ if (own.length === 1)
54
+ return { kind: "fill", id: own[0].id, reason: "single" };
55
+ const defaults = own.filter((w) => w.isDefault === true);
56
+ if (defaults.length === 1)
57
+ return { kind: "fill", id: defaults[0].id, reason: "default" };
58
+ return { kind: "choice_needed" };
59
+ }
60
+ export function decideGln(input) {
61
+ const stored = input.stored?.trim() || null;
62
+ const remote = input.fromFloriday?.trim() || null;
63
+ if (!remote)
64
+ return { kind: "unavailable", stored };
65
+ const err = validateGln(remote);
66
+ if (err)
67
+ return { kind: "invalid", stored, fromFloriday: remote, error: err };
68
+ if (!stored)
69
+ return { kind: "fill", gln: remote };
70
+ if (stored === remote)
71
+ return { kind: "keep", gln: stored };
72
+ return { kind: "mismatch", stored, fromFloriday: remote };
73
+ }
@@ -26,4 +26,11 @@ export declare const FLORYDAY_CLOCK_SUPPLY_PERMISSION_MESSAGE = "Floriday heeft
26
26
  export declare const FLORYDAY_CONTRACT_PERMISSION_MESSAGE = "Floriday heeft de Contracten-module nog niet vrijgegeven voor deze koppeling. Laat dit recht in Floriday activeren en vernieuw daarna de verbinding.";
27
27
  export declare const FLORYDAY_SALES_STRATEGY_PERMISSION_MESSAGE = "Floriday heeft Verkoopstrategie\u00EBn nog niet vrijgegeven voor deze koppeling. Laat het recht `sales-strategy:read` in Floriday activeren en vernieuw daarna de verbinding.";
28
28
  export declare const FLORYDAY_SUPPLY_PERMISSION_MESSAGE = "Floriday heeft het recht `supply:read` (aanvoerregels met prijzen) nog niet vrijgegeven voor deze koppeling. Laat dit recht in Floriday activeren en vernieuw daarna de verbinding.";
29
+ /**
30
+ * Toont een Floriday-foutbody/-bericht aantoonbaar een ontbrekend recht/scope?
31
+ * Een kale `403 Forbidden` (bv. onbekend pad bij Floriday's voordeur) telt NIET.
32
+ */
33
+ export declare function floridayErrorShowsMissingPermission(text: string | null | undefined): boolean;
34
+ /** Scopes uit een JWT-toegangstoken lezen (alleen payload; token nooit loggen). */
35
+ export declare function scopesFromAccessToken(token: string): string[] | null;
29
36
  //# sourceMappingURL=floriday-scopes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"floriday-scopes.d.ts","sourceRoot":"","sources":["../../src/lib/floriday-scopes.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,wBAAwB,2QAe3B,CAAC;AAOX,eAAO,MAAM,4BAA4B,sDAG/B,CAAC;AACX,eAAO,MAAM,iCAAiC,gCAAiC,CAAC;AAChF,eAAO,MAAM,kCAAkC,iCAAkC,CAAC;AAElF,eAAO,MAAM,wBAAwB,8CAG3B,CAAC;AACX,eAAO,MAAM,6BAA6B,4BAA6B,CAAC;AACxE,eAAO,MAAM,8BAA8B,6BAA8B,CAAC;AAM1E,eAAO,MAAM,8BAA8B,0DAGjC,CAAC;AACX,eAAO,MAAM,mCAAmC,kCAAmC,CAAC;AACpF,eAAO,MAAM,oCAAoC,mCAAoC,CAAC;AAKtF,eAAO,MAAM,uBAAuB,QAAqC,CAAC;AAE1E,eAAO,MAAM,2BAA2B,QAG7B,CAAC;AACZ,eAAO,MAAM,gCAAgC,QAGlC,CAAC;AACZ,eAAO,MAAM,iCAAiC,QAGnC,CAAC;AAEZ,eAAO,MAAM,uBAAuB,QAGzB,CAAC;AACZ,eAAO,MAAM,4BAA4B,QAG9B,CAAC;AACZ,eAAO,MAAM,6BAA6B,QAG/B,CAAC;AAEZ,eAAO,MAAM,6BAA6B,QAG/B,CAAC;AACZ,eAAO,MAAM,kCAAkC,QAGpC,CAAC;AACZ,eAAO,MAAM,mCAAmC,QAGrC,CAAC;AAGZ,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAQ,GAAG,MAAM,CA4C5E;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED,eAAO,MAAM,wCAAwC,mJAC6F,CAAC;AAEnJ,eAAO,MAAM,oCAAoC,yJACuG,CAAC;AAEzJ,eAAO,MAAM,0CAA0C,kLACqH,CAAC;AAE7K,eAAO,MAAM,kCAAkC,wLACwI,CAAC"}
1
+ {"version":3,"file":"floriday-scopes.d.ts","sourceRoot":"","sources":["../../src/lib/floriday-scopes.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,wBAAwB,2QAe3B,CAAC;AAOX,eAAO,MAAM,4BAA4B,sDAG/B,CAAC;AACX,eAAO,MAAM,iCAAiC,gCAAiC,CAAC;AAChF,eAAO,MAAM,kCAAkC,iCAAkC,CAAC;AAElF,eAAO,MAAM,wBAAwB,8CAG3B,CAAC;AACX,eAAO,MAAM,6BAA6B,4BAA6B,CAAC;AACxE,eAAO,MAAM,8BAA8B,6BAA8B,CAAC;AAM1E,eAAO,MAAM,8BAA8B,0DAGjC,CAAC;AACX,eAAO,MAAM,mCAAmC,kCAAmC,CAAC;AACpF,eAAO,MAAM,oCAAoC,mCAAoC,CAAC;AAKtF,eAAO,MAAM,uBAAuB,QAAqC,CAAC;AAE1E,eAAO,MAAM,2BAA2B,QAG7B,CAAC;AACZ,eAAO,MAAM,gCAAgC,QAGlC,CAAC;AACZ,eAAO,MAAM,iCAAiC,QAGnC,CAAC;AAEZ,eAAO,MAAM,uBAAuB,QAGzB,CAAC;AACZ,eAAO,MAAM,4BAA4B,QAG9B,CAAC;AACZ,eAAO,MAAM,6BAA6B,QAG/B,CAAC;AAEZ,eAAO,MAAM,6BAA6B,QAG/B,CAAC;AACZ,eAAO,MAAM,kCAAkC,QAGpC,CAAC;AACZ,eAAO,MAAM,mCAAmC,QAGrC,CAAC;AAGZ,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAQ,GAAG,MAAM,CA4C5E;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED,eAAO,MAAM,wCAAwC,mJAC6F,CAAC;AAEnJ,eAAO,MAAM,oCAAoC,yJACuG,CAAC;AAEzJ,eAAO,MAAM,0CAA0C,kLACqH,CAAC;AAE7K,eAAO,MAAM,kCAAkC,wLACwI,CAAC;AAExL;;;GAGG;AACH,wBAAgB,mCAAmC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAY5F;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAapE"}
@@ -129,3 +129,37 @@ export const FLORYDAY_CLOCK_SUPPLY_PERMISSION_MESSAGE = "Floriday heeft Klokaanv
129
129
  export const FLORYDAY_CONTRACT_PERMISSION_MESSAGE = "Floriday heeft de Contracten-module nog niet vrijgegeven voor deze koppeling. Laat dit recht in Floriday activeren en vernieuw daarna de verbinding.";
130
130
  export const FLORYDAY_SALES_STRATEGY_PERMISSION_MESSAGE = "Floriday heeft Verkoopstrategieën nog niet vrijgegeven voor deze koppeling. Laat het recht `sales-strategy:read` in Floriday activeren en vernieuw daarna de verbinding.";
131
131
  export const FLORYDAY_SUPPLY_PERMISSION_MESSAGE = "Floriday heeft het recht `supply:read` (aanvoerregels met prijzen) nog niet vrijgegeven voor deze koppeling. Laat dit recht in Floriday activeren en vernieuw daarna de verbinding.";
132
+ /**
133
+ * Toont een Floriday-foutbody/-bericht aantoonbaar een ontbrekend recht/scope?
134
+ * Een kale `403 Forbidden` (bv. onbekend pad bij Floriday's voordeur) telt NIET.
135
+ */
136
+ export function floridayErrorShowsMissingPermission(text) {
137
+ const t = String(text ?? "").toLowerCase();
138
+ return (t.includes("insufficient_scope") ||
139
+ t.includes("insufficient scope") ||
140
+ t.includes("missing scope") ||
141
+ t.includes("scope") ||
142
+ t.includes("permission") ||
143
+ t.includes("policy evaluation failed") ||
144
+ t.includes("access_denied") ||
145
+ t.includes("not authorized for"));
146
+ }
147
+ /** Scopes uit een JWT-toegangstoken lezen (alleen payload; token nooit loggen). */
148
+ export function scopesFromAccessToken(token) {
149
+ try {
150
+ const part = token.split(".")[1];
151
+ if (!part)
152
+ return null;
153
+ const b64 = part.replace(/-/g, "+").replace(/_/g, "/");
154
+ const json = JSON.parse(atob(b64 + "=".repeat((4 - (b64.length % 4)) % 4)));
155
+ const s = json.scope ?? json.scp;
156
+ if (Array.isArray(s))
157
+ return s.map(String);
158
+ if (typeof s === "string")
159
+ return s.split(/\s+/).filter(Boolean);
160
+ return null;
161
+ }
162
+ catch {
163
+ return null;
164
+ }
165
+ }