@flowselections/core 1.0.30 → 1.0.32

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 (36) hide show
  1. package/README.md +40 -1
  2. package/dist/auth/header-identity.d.ts +23 -0
  3. package/dist/auth/header-identity.d.ts.map +1 -0
  4. package/dist/auth/header-identity.js +53 -0
  5. package/dist/auth/password-callback.d.ts +17 -0
  6. package/dist/auth/password-callback.d.ts.map +1 -0
  7. package/dist/auth/password-callback.js +39 -0
  8. package/dist/components/layout/AuthenticatedLayout.js +1 -1
  9. package/dist/components/layout/Header.d.ts +3 -1
  10. package/dist/components/layout/Header.d.ts.map +1 -1
  11. package/dist/components/layout/Header.js +5 -2
  12. package/dist/components/layout/HeaderIdentity.d.ts +5 -0
  13. package/dist/components/layout/HeaderIdentity.d.ts.map +1 -0
  14. package/dist/components/layout/HeaderIdentity.js +32 -0
  15. package/dist/components/layout/ResetWachtwoordPage.d.ts.map +1 -1
  16. package/dist/components/layout/ResetWachtwoordPage.js +63 -13
  17. package/dist/components/layout/useHeaderGreeting.d.ts +3 -0
  18. package/dist/components/layout/useHeaderGreeting.d.ts.map +1 -0
  19. package/dist/components/layout/useHeaderGreeting.js +40 -0
  20. package/dist/components/ui/command.d.ts +7 -7
  21. package/dist/server/index.d.ts +1 -1
  22. package/dist/server/index.d.ts.map +1 -1
  23. package/dist/server/index.js +1 -1
  24. package/dist/server/shell-runtime.d.ts +10 -4
  25. package/dist/server/shell-runtime.d.ts.map +1 -1
  26. package/dist/server/shell-runtime.js +266 -29
  27. package/dist/supabase/auth-middleware.d.ts +30 -0
  28. package/dist/supabase/auth-middleware.d.ts.map +1 -1
  29. package/dist/supabase/client.d.ts +30 -0
  30. package/dist/supabase/client.d.ts.map +1 -1
  31. package/dist/supabase/client.js +2 -0
  32. package/dist/supabase/client.server.d.ts +30 -0
  33. package/dist/supabase/client.server.d.ts.map +1 -1
  34. package/dist/supabase/types.d.ts +30 -0
  35. package/dist/supabase/types.d.ts.map +1 -1
  36. package/package.json +1 -1
package/README.md CHANGED
@@ -213,7 +213,46 @@ De door het dashboard gegenereerde globale request-middleware roept per verzoek
213
213
 
214
214
  ```ts
215
215
  import { runShellRuntime } from "@flowselections/core/server";
216
- await runShellRuntime(); // zet PUBLIC_BASE_URL uit de database, controleert de Central-koppeling hooguit eens per uur
216
+ await runShellRuntime(); // bestaand gedrag, geeft null terug; geen croncontrole zonder context
217
217
  ```
218
218
 
219
219
  Vereist `SUPABASE_URL`, `SB_SERVICE_ROLE_KEY`, `CENTRAL_AUTH_URL`, `CENTRAL_AUTH_SECRET`. Gooit nooit; de leessleutel wordt nooit gelogd of teruggegeven.
220
+
221
+ ### Centrale croncontrole (dashboardaansluiting)
222
+
223
+ Het dashboard past `system-files.js`, `buildBody`, voor het systeembestand
224
+ `src/lib/shell-runtime-middleware.ts` aan. Houd deze middleware als eerste:
225
+
226
+ ```ts
227
+ createMiddleware({ type: "request" }).server(async ({ request, pathname, next }) => {
228
+ const { runShellRuntime } = await import("@flowselections/core/server");
229
+ const denied = await runShellRuntime({ request, pathname }); return denied ?? next();
230
+ });
231
+ ```
232
+
233
+ `ensureCronKeys()` synchroniseert `SUPABASE_PUBLISHABLE_KEY ?? SUPABASE_ANON_KEY`
234
+ (zonder omringende aanhalingstekens) via `flowselections_set_anon_key`.
235
+ Central-koppeling en sleutelsynchronisatie wachten één uur na succes en vijf
236
+ minuten na een mislukking, per isolate. Geen sleutels in logs of responses.
237
+
238
+ `checkCronRequest(request)` controleert uitsluitend exacte paden uit
239
+ `flowselections_cron_paths()`, met `x-flowselections-cron-key` tegen
240
+ `flowselections_cron_secret()`. Native Web Crypto verifieert de vergelijking.
241
+ Paden, sleutel en configuratie worden vijf minuten gecachet; een afwijkende
242
+ sleutel veroorzaakt één herlezing vóór de beslissing. Gelijktijdige herlezingen
243
+ delen een Promise; daarna blijven herhaalde mismatches tot de volgende cacheverversing
244
+ zonder extra RPC's, om een verzoekenstorm te voorkomen. Ontbrekende RPC's, netwerkfouten of ongeldige antwoorden laten
245
+ verzoeken door en worden begrensd gemeld; deze laag vervangt geen modulebeveiliging.
246
+
247
+ `module_runtime_config` heeft `key`/`value`-rijen: `shell_base_url`,
248
+ `cron_auth_mode` (`compat` standaard, of `afdwingen`) en
249
+ `cron_auth_exempt_paths` (komma-gescheiden extra padprefixen). In `compat` komt
250
+ alleen een waarschuwing; in `afdwingen` een 401 vóór de handler. De drie vaste
251
+ uitzonderingsprefixen blijven altijd gelden, ook bij een lege configuratie:
252
+
253
+ - `/api/public/print/queue-tick`
254
+ - `/api/public/hooks/logistics-shipping-note-now`
255
+ - `/api/public/cron/ping`
256
+
257
+ De eigen engine-tick en `verkoop_cron_post` staan buiten deze centrale padenlijst.
258
+ Er worden geen requestheaders herschreven en geen modules gewijzigd.
@@ -0,0 +1,23 @@
1
+ type IdentitySource = {
2
+ profile?: {
3
+ display_name?: unknown;
4
+ } | null;
5
+ user?: {
6
+ email?: string | null;
7
+ user_metadata?: Record<string, unknown>;
8
+ } | null;
9
+ };
10
+ export declare function displayName(auth: IdentitySource): string;
11
+ export declare function primaryRole(roles: readonly string[]): string | undefined;
12
+ export declare function roleLabel(key: string | undefined, name?: string | null): string;
13
+ export declare function initials(name: string): string;
14
+ export declare function greetingAtHour(hour: number): string;
15
+ export declare function amsterdamGreeting(now: Date, name: string): {
16
+ date: string;
17
+ text: string;
18
+ };
19
+ export type GreetingStorage = Pick<Storage, "getItem" | "setItem">;
20
+ /** Storage is obtained inside the callback so even localStorage access can fail safely. */
21
+ export declare function claimGreeting(userId: string, date: string, storage: () => GreetingStorage, seen: Map<string, string>): boolean;
22
+ export {};
23
+ //# sourceMappingURL=header-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header-identity.d.ts","sourceRoot":"","sources":["../../src/core/auth/header-identity.ts"],"names":[],"mappings":"AAAA,KAAK,cAAc,GAAG;IACpB,OAAO,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC;CAClF,CAAC;AAEF,wBAAgB,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAIxD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAExE;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAG/E;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK7C;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAUzF;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC,CAAC;AAEnE,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,eAAe,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAgB9H"}
@@ -0,0 +1,53 @@
1
+ export function displayName(auth) {
2
+ const metadata = auth.user?.user_metadata;
3
+ const candidates = [auth.profile?.display_name, metadata?.display_name, metadata?.full_name, auth.user?.email?.split("@")[0]];
4
+ return candidates.find((value) => typeof value === "string" && !!value.trim())?.trim() ?? "Gebruiker";
5
+ }
6
+ export function primaryRole(roles) {
7
+ return ["superadmin", "admin", "planner", "monteur"].find((role) => roles.includes(role)) ?? roles.find((role) => !!role.trim());
8
+ }
9
+ export function roleLabel(key, name) {
10
+ const value = name?.trim() || key || "";
11
+ return value ? value.charAt(0).toLocaleUpperCase("nl-NL") + value.slice(1) : "";
12
+ }
13
+ export function initials(name) {
14
+ const words = name.trim().split(/\s+/);
15
+ const first = words[0]?.charAt(0) ?? "";
16
+ const last = words.length > 1 ? words[words.length - 1]?.charAt(0) ?? "" : "";
17
+ return (first + last).toLocaleUpperCase("nl-NL");
18
+ }
19
+ export function greetingAtHour(hour) {
20
+ return hour >= 5 && hour < 12 ? "Goedemorgen" : hour >= 12 && hour < 18 ? "Goedemiddag" : "Goedenavond";
21
+ }
22
+ export function amsterdamGreeting(now, name) {
23
+ const parts = new Intl.DateTimeFormat("nl-NL", {
24
+ timeZone: "Europe/Amsterdam", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", hourCycle: "h23",
25
+ }).formatToParts(now);
26
+ const part = (type) => parts.find((value) => value.type === type)?.value ?? "";
27
+ const date = `${part("year")}-${part("month")}-${part("day")}`;
28
+ const label = new Intl.DateTimeFormat("nl-NL", {
29
+ timeZone: "Europe/Amsterdam", weekday: "long", day: "numeric", month: "long",
30
+ }).format(now);
31
+ return { date, text: `${greetingAtHour(Number(part("hour")))}, ${name.split(/\s+/)[0]} · ${label}` };
32
+ }
33
+ /** Storage is obtained inside the callback so even localStorage access can fail safely. */
34
+ export function claimGreeting(userId, date, storage, seen) {
35
+ if (seen.get(userId) === date)
36
+ return false;
37
+ try {
38
+ const target = storage();
39
+ const key = `core:greeted:${userId}`;
40
+ if (target.getItem(key) === date) {
41
+ seen.set(userId, date);
42
+ return false;
43
+ }
44
+ target.setItem(key, date);
45
+ }
46
+ catch {
47
+ // Without storage, only one greeting per user for this page session.
48
+ if (seen.has(userId))
49
+ return false;
50
+ }
51
+ seen.set(userId, date);
52
+ return true;
53
+ }
@@ -0,0 +1,17 @@
1
+ /** URL metadata is only a correlation signal; Supabase validates the session. */
2
+ export type PasswordCallback = {
3
+ type: "invite" | "recovery";
4
+ sub: string;
5
+ };
6
+ export declare const PASSWORD_CALLBACK_WAIT_MS = 5000;
7
+ export declare function readPasswordCallback(hash: string): PasswordCallback | null;
8
+ /** Called immediately before createClient can consume/clear the URL hash. */
9
+ export declare function capturePasswordCallback(): void;
10
+ export declare function getPasswordCallback(): PasswordCallback | null;
11
+ export declare function clearPasswordCallback(): void;
12
+ export declare function matchingPasswordCallback(signal: PasswordCallback | null, session: {
13
+ user: {
14
+ id: string;
15
+ };
16
+ } | null): PasswordCallback["type"] | null;
17
+ //# sourceMappingURL=password-callback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-callback.d.ts","sourceRoot":"","sources":["../../src/core/auth/password-callback.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,GAAG;IAAE,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5E,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAG9C,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAiB1E;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,IAAI,IAAI,CAG9C;AAED,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,IAAI,CAE7D;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,gBAAgB,GAAG,IAAI,EAC/B,OAAO,EAAE;IAAE,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAAG,IAAI,GACvC,gBAAgB,CAAC,MAAM,CAAC,GAAG,IAAI,CAEjC"}
@@ -0,0 +1,39 @@
1
+ export const PASSWORD_CALLBACK_WAIT_MS = 5000;
2
+ let callback = null;
3
+ export function readPasswordCallback(hash) {
4
+ const params = new URLSearchParams(hash.replace(/^#/, ""));
5
+ const type = params.get("type");
6
+ if (type !== "invite" && type !== "recovery")
7
+ return null;
8
+ try {
9
+ // Decode only the payload inline. Never retain the access token.
10
+ const payload = params.get("access_token")?.split(".")[1];
11
+ params.delete("access_token");
12
+ if (!payload)
13
+ return null;
14
+ const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
15
+ const bytes = Uint8Array.from(atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "=")), (c) => c.charCodeAt(0));
16
+ const decoded = JSON.parse(new TextDecoder().decode(bytes));
17
+ if (typeof decoded !== "object" || decoded === null || !("sub" in decoded))
18
+ return null;
19
+ return typeof decoded.sub === "string" && decoded.sub.length > 0 ? { type, sub: decoded.sub } : null;
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ /** Called immediately before createClient can consume/clear the URL hash. */
26
+ export function capturePasswordCallback() {
27
+ if (typeof window === "undefined")
28
+ return;
29
+ callback = readPasswordCallback(window.location.hash);
30
+ }
31
+ export function getPasswordCallback() {
32
+ return callback;
33
+ }
34
+ export function clearPasswordCallback() {
35
+ callback = null;
36
+ }
37
+ export function matchingPasswordCallback(signal, session) {
38
+ return signal && session?.user.id === signal.sub ? signal.type : null;
39
+ }
@@ -45,5 +45,5 @@ export function AuthenticatedLayout({ children, header = true, permissions = tru
45
45
  visibleModules,
46
46
  permissionsLoading: loading,
47
47
  }), [sidebarProps.shellId, sidebarProps.modules, visibleModules, loading]);
48
- return (_jsx(ShellContext.Provider, { value: shell, children: _jsxs("div", { className: "min-h-screen bg-background [--core-topbar-h:3.5rem] md:[--core-topbar-h:0px]", children: [_jsx(Sidebar, { ...sidebarProps, permissions: permissions, visibleModules: visibleModules, loading: loading }), _jsxs("div", { className: "flex min-h-dvh flex-col pt-[var(--core-topbar-h)] md:ml-64 md:pt-0", children: [header && _jsx(Header, { title: title }), _jsx("main", { className: "flex-1", children: permissions && access === "none" ? _jsx(NoAccess, {}) : (children ?? _jsx(Outlet, {})) })] })] }) }));
48
+ return (_jsx(ShellContext.Provider, { value: shell, children: _jsxs("div", { className: "min-h-screen bg-background [--core-topbar-h:3.5rem] md:[--core-topbar-h:0px]", children: [_jsx(Sidebar, { ...sidebarProps, permissions: permissions, visibleModules: visibleModules, loading: loading }), _jsxs("div", { className: "flex min-h-dvh flex-col pt-[var(--core-topbar-h)] md:ml-64 md:pt-0", children: [header && _jsx(Header, { title: title, auth: sidebarProps.auth }), _jsx("main", { className: "flex-1", children: permissions && access === "none" ? _jsx(NoAccess, {}) : (children ?? _jsx(Outlet, {})) })] })] }) }));
49
49
  }
@@ -1,6 +1,8 @@
1
+ import type { AuthContext } from "../../auth/useAuth.js";
1
2
  interface HeaderProps {
2
3
  title: string;
4
+ auth?: AuthContext;
3
5
  }
4
- export declare function Header({ title }: HeaderProps): import("react/jsx-runtime").JSX.Element;
6
+ export declare function Header({ title, auth }: HeaderProps): import("react/jsx-runtime").JSX.Element;
5
7
  export {};
6
8
  //# sourceMappingURL=Header.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Header.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/Header.tsx"],"names":[],"mappings":"AAOA,UAAU,WAAW;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,WAAW,2CAsB5C"}
1
+ {"version":3,"file":"Header.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/Header.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIzD,UAAU,WAAW;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED,wBAAgB,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,WAAW,2CA4BlD"}
@@ -5,7 +5,10 @@ import { Input } from "../ui/input.js";
5
5
  import { Button } from "../ui/button.js";
6
6
  import { BugsDialog } from "./BugsDialog.js";
7
7
  import { NotificationBell } from "./NotificationBell.js";
8
- export function Header({ title }) {
8
+ import { HeaderIdentity } from "./HeaderIdentity.js";
9
+ import { useHeaderGreeting } from "./useHeaderGreeting.js";
10
+ export function Header({ title, auth }) {
9
11
  const [bugsDialogOpen, setBugsDialogOpen] = useState(false);
10
- return (_jsxs(_Fragment, { children: [_jsxs("header", { className: "sticky top-[var(--core-topbar-h,0px)] md:top-0 z-30 flex h-[var(--app-header-height)] shrink-0 items-center justify-between border-b border-border bg-card px-6", children: [_jsx("h1", { className: "text-xl font-semibold text-foreground", children: title }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "relative hidden md:block", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoeken...", className: "w-64 pl-9" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setBugsDialogOpen(true), children: [_jsx(Bug, { className: "mr-2 h-4 w-4" }), "Bugs melden"] }), _jsx(NotificationBell, {})] })] }), _jsx(BugsDialog, { open: bugsDialogOpen, onOpenChange: setBugsDialogOpen })] }));
12
+ const greeting = useHeaderGreeting(auth);
13
+ return (_jsxs(_Fragment, { children: [_jsx("header", { className: "@container sticky top-[var(--core-topbar-h,0px)] md:top-0 z-30 min-h-[var(--app-header-height)] shrink-0 border-b border-border bg-card px-3 py-2 md:px-6", children: _jsxs("div", { className: "flex min-h-11 flex-wrap items-center justify-between gap-x-3 gap-y-1", children: [_jsx("h1", { className: "min-w-0 flex-1 truncate text-xl font-semibold text-foreground", children: title }), _jsxs("div", { className: "flex shrink-0 items-center gap-2 @[900px]:gap-4", children: [_jsxs("div", { className: "relative hidden @[1300px]:block", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoeken...", className: "w-64 pl-9" })] }), _jsxs(Button, { variant: "outline", size: "sm", "aria-label": "Bugs melden", title: "Bugs melden", onClick: () => setBugsDialogOpen(true), children: [_jsx(Bug, { className: "h-4 w-4" }), _jsx("span", { className: "hidden @[650px]:inline", children: "Bugs melden" })] }), _jsx(NotificationBell, {}), greeting && _jsx("p", { className: "hidden max-w-96 font-sans text-sm text-muted-foreground @[1100px]:block", children: greeting }), auth && _jsx(HeaderIdentity, { auth: auth })] }), greeting && _jsx("p", { className: "w-full break-words font-sans text-xs text-muted-foreground @[1100px]:hidden", children: greeting })] }) }), _jsx(BugsDialog, { open: bugsDialogOpen, onOpenChange: setBugsDialogOpen })] }));
11
14
  }
@@ -0,0 +1,5 @@
1
+ import type { AuthContext } from "../../auth/useAuth.js";
2
+ export declare function HeaderIdentity({ auth }: {
3
+ auth: AuthContext;
4
+ }): import("react/jsx-runtime").JSX.Element | null;
5
+ //# sourceMappingURL=HeaderIdentity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HeaderIdentity.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/HeaderIdentity.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAOzD,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,kDAkC7D"}
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { displayName, primaryRole, roleLabel, initials } from "../../auth/header-identity.js";
4
+ import { supabase } from "../../supabase/client.js";
5
+ import { Button } from "../ui/button.js";
6
+ import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
7
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip.js";
8
+ export function HeaderIdentity({ auth }) {
9
+ const name = displayName(auth);
10
+ const key = primaryRole(auth.roles);
11
+ const [resolved, setResolved] = useState(null);
12
+ useEffect(() => {
13
+ let active = true;
14
+ if (!key || !auth.user)
15
+ return;
16
+ const userId = auth.user.id;
17
+ void (async () => {
18
+ try {
19
+ const { data } = await supabase.from("roles").select("name").eq("key", key).maybeSingle();
20
+ if (active && data?.name)
21
+ setResolved({ key, name: data.name, userId });
22
+ }
23
+ catch { /* Existing permissions only; the key remains the fallback. */ }
24
+ })();
25
+ return () => { active = false; };
26
+ }, [key, auth.user?.id]);
27
+ if (!auth.user)
28
+ return null;
29
+ const role = roleLabel(key, resolved && resolved.key === key && resolved.userId === auth.user.id ? resolved.name : undefined);
30
+ const label = role ? `${name} · ${role}` : name;
31
+ return _jsxs(_Fragment, { children: [_jsxs("div", { className: "hidden max-w-64 min-w-0 items-center gap-1 font-sans text-sm @[1100px]:flex", title: label, children: [_jsx("span", { className: "truncate text-foreground", children: name }), role && _jsxs("span", { className: "shrink-0 text-muted-foreground", children: ["\u00B7 ", role] })] }), _jsx(TooltipProvider, { children: _jsx(Tooltip, { children: _jsxs(Popover, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { variant: "secondary", size: "icon", className: "shrink-0 rounded-full font-sans @[1100px]:hidden", "aria-label": label, children: initials(name) }) }) }), _jsxs(PopoverContent, { align: "end", className: "w-64 break-words font-sans", children: [_jsx("p", { children: name }), role && _jsx("p", { className: "text-sm text-muted-foreground", children: role })] }), _jsx(TooltipContent, { children: label })] }) }) })] });
32
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"ResetWachtwoordPage.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/ResetWachtwoordPage.tsx"],"names":[],"mappings":"AASA,UAAU,wBAAwB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,UAAU,EAAE,SAAoB,EAAE,EAAE,wBAAwB,2CAwFjG"}
1
+ {"version":3,"file":"ResetWachtwoordPage.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/ResetWachtwoordPage.tsx"],"names":[],"mappings":"AAUA,UAAU,wBAAwB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,UAAU,EAAE,SAAoB,EAAE,EAAE,wBAAwB,2CA0IjG"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect } from "react";
2
+ import { useState, useEffect, useRef } from "react";
3
3
  import { useRouter } from "@tanstack/react-router";
4
4
  import { Button } from "../ui/button.js";
5
5
  import { Input } from "../ui/input.js";
@@ -7,23 +7,58 @@ import { Label } from "../ui/label.js";
7
7
  import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../ui/card.js";
8
8
  import { toast } from "sonner";
9
9
  import { supabase } from "../../supabase/client.js";
10
+ import { clearPasswordCallback, getPasswordCallback, matchingPasswordCallback, PASSWORD_CALLBACK_WAIT_MS } from "../../auth/password-callback.js";
10
11
  export function ResetWachtwoordPage({ redirectTo, loginPath = "/login" }) {
11
12
  const router = useRouter();
12
13
  const [password, setPassword] = useState("");
13
14
  const [confirmPassword, setConfirmPassword] = useState("");
14
15
  const [isLoading, setIsLoading] = useState(false);
15
- const [isReady, setIsReady] = useState(false);
16
+ const [callbackType, setCallbackType] = useState(null);
17
+ const [isWaiting, setIsWaiting] = useState(true);
18
+ const callback = useRef(null);
16
19
  useEffect(() => {
17
- const hash = window.location.hash;
18
- if (hash && hash.includes("type=recovery")) {
19
- setIsReady(true);
20
- }
21
- const { data: { subscription } } = supabase.auth.onAuthStateChange((event) => {
22
- if (event === "PASSWORD_RECOVERY") {
23
- setIsReady(true);
20
+ let active = true;
21
+ let accepted = false;
22
+ let timer;
23
+ // Accessing auth initializes the lazy client and captures the URL first.
24
+ const auth = supabase.auth;
25
+ callback.current = callback.current ?? getPasswordCallback();
26
+ const signal = callback.current;
27
+ const acceptSession = (session) => {
28
+ if (!active)
29
+ return;
30
+ const type = matchingPasswordCallback(signal, session);
31
+ setCallbackType(type);
32
+ if (type) {
33
+ accepted = true;
34
+ clearTimeout(timer);
35
+ clearPasswordCallback();
36
+ setIsWaiting(false);
37
+ }
38
+ else if (accepted) {
39
+ // A later sign-out or account switch invalidates this callback.
40
+ active = false;
41
+ callback.current = null;
42
+ setIsWaiting(false);
24
43
  }
44
+ };
45
+ const { data: { subscription } } = auth.onAuthStateChange((_event, session) => {
46
+ acceptSession(session);
25
47
  });
26
- return () => subscription.unsubscribe();
48
+ timer = setTimeout(() => {
49
+ active = false;
50
+ clearPasswordCallback();
51
+ callback.current = null;
52
+ setCallbackType(null);
53
+ setIsWaiting(false);
54
+ }, PASSWORD_CALLBACK_WAIT_MS);
55
+ void auth.getSession().then(({ data: { session }, error }) => {
56
+ if (!error)
57
+ acceptSession(session);
58
+ }).catch(() => {
59
+ // The bounded wait also covers session initialization/network failures.
60
+ });
61
+ return () => { active = false; clearTimeout(timer); clearPasswordCallback(); subscription.unsubscribe(); };
27
62
  }, []);
28
63
  const handleSubmit = async (e) => {
29
64
  e.preventDefault();
@@ -35,19 +70,34 @@ export function ResetWachtwoordPage({ redirectTo, loginPath = "/login" }) {
35
70
  toast.error("Wachtwoorden komen niet overeen");
36
71
  return;
37
72
  }
73
+ if (!callbackType)
74
+ return;
38
75
  setIsLoading(true);
76
+ const { data: { session }, error: sessionError } = await supabase.auth.getSession();
77
+ if (sessionError || !matchingPasswordCallback(callback.current, session)) {
78
+ clearPasswordCallback();
79
+ callback.current = null;
80
+ setCallbackType(null);
81
+ setIsLoading(false);
82
+ return;
83
+ }
39
84
  const { error } = await supabase.auth.updateUser({ password });
40
85
  setIsLoading(false);
41
86
  if (error) {
42
87
  toast.error("Fout bij het instellen van het nieuwe wachtwoord");
43
88
  }
44
89
  else {
90
+ clearPasswordCallback();
91
+ callback.current = null;
45
92
  toast.success("Wachtwoord succesvol gewijzigd!");
46
93
  await router.navigate({ href: redirectTo });
47
94
  }
48
95
  };
49
- if (!isReady) {
50
- return (_jsx("div", { className: "min-h-screen flex items-center justify-center bg-background p-4", children: _jsxs(Card, { className: "w-full max-w-md", children: [_jsxs(CardHeader, { className: "text-center", children: [_jsx(CardTitle, { "data-empty-state": true, className: "text-2xl", children: "Ongeldige link" }), _jsx(CardDescription, { children: "Deze reset-link is ongeldig of verlopen. Vraag een nieuwe aan via de inlogpagina." })] }), _jsx(CardFooter, { className: "justify-center", children: _jsx(Button, { onClick: () => router.navigate({ href: loginPath }), children: "Naar inlogpagina" }) })] }) }));
96
+ if (isWaiting) {
97
+ return (_jsx("div", { className: "min-h-screen flex items-center justify-center bg-background p-4", role: "status", "aria-label": "Link controleren", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-b-2 border-primary motion-reduce:animate-none" }) }));
98
+ }
99
+ if (!callbackType) {
100
+ return (_jsx("div", { className: "min-h-screen flex items-center justify-center bg-background p-4", children: _jsxs(Card, { className: "w-full max-w-md", children: [_jsxs(CardHeader, { className: "text-center", children: [_jsx(CardTitle, { "data-empty-state": true, className: "text-2xl", children: "Ongeldige link" }), _jsx(CardDescription, { children: "Deze link is verlopen of al gebruikt. Vraag een nieuwe uitnodiging aan, of gebruik Wachtwoord vergeten op de inlogpagina." })] }), _jsx(CardFooter, { className: "justify-center", children: _jsx(Button, { onClick: () => router.navigate({ href: loginPath }), children: "Naar inlogpagina" }) })] }) }));
51
101
  }
52
- return (_jsx("div", { className: "min-h-screen flex items-center justify-center bg-background p-4", children: _jsxs(Card, { className: "w-full max-w-md", children: [_jsxs(CardHeader, { className: "text-center", children: [_jsx(CardTitle, { className: "text-2xl", children: "Nieuw wachtwoord instellen" }), _jsx(CardDescription, { children: "Kies een nieuw wachtwoord voor je account" })] }), _jsxs("form", { onSubmit: handleSubmit, children: [_jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "password", children: "Nieuw wachtwoord" }), _jsx(Input, { id: "password", type: "password", value: password, onChange: (e) => setPassword(e.target.value), required: true, minLength: 6, maxLength: 128 })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "confirmPassword", children: "Bevestig wachtwoord" }), _jsx(Input, { id: "confirmPassword", type: "password", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), required: true, minLength: 6, maxLength: 128 })] })] }), _jsx(CardFooter, { children: _jsx(Button, { type: "submit", className: "w-full", disabled: isLoading, children: isLoading ? "Opslaan..." : "Wachtwoord opslaan" }) })] })] }) }));
102
+ return (_jsx("div", { className: "min-h-screen flex items-center justify-center bg-background p-4", children: _jsxs(Card, { className: "w-full max-w-md", children: [_jsxs(CardHeader, { className: "text-center", children: [_jsx(CardTitle, { className: "text-2xl", children: callbackType === "invite" ? "Welkom!" : "Nieuw wachtwoord instellen" }), _jsx(CardDescription, { children: callbackType === "invite" ? "Kies een wachtwoord om je account te activeren." : "Kies een nieuw wachtwoord voor je account" })] }), _jsxs("form", { onSubmit: handleSubmit, children: [_jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "password", children: "Nieuw wachtwoord" }), _jsx(Input, { id: "password", type: "password", value: password, onChange: (e) => setPassword(e.target.value), required: true, minLength: 6, maxLength: 128 })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "confirmPassword", children: "Bevestig wachtwoord" }), _jsx(Input, { id: "confirmPassword", type: "password", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), required: true, minLength: 6, maxLength: 128 })] })] }), _jsx(CardFooter, { children: _jsx(Button, { type: "submit", className: "w-full", disabled: isLoading, children: isLoading ? "Opslaan..." : "Wachtwoord opslaan" }) })] })] }) }));
53
103
  }
@@ -0,0 +1,3 @@
1
+ import type { AuthContext } from "../../auth/useAuth.js";
2
+ export declare function useHeaderGreeting(auth?: AuthContext): string | null;
3
+ //# sourceMappingURL=useHeaderGreeting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useHeaderGreeting.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/useHeaderGreeting.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAKzD,wBAAgB,iBAAiB,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CA+BnE"}
@@ -0,0 +1,40 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { useLocation } from "@tanstack/react-router";
3
+ import { amsterdamGreeting, claimGreeting, displayName } from "../../auth/header-identity.js";
4
+ const seen = new Map();
5
+ export function useHeaderGreeting(auth) {
6
+ const href = useLocation({ select: (location) => location.href });
7
+ const [greeting, setGreeting] = useState(null);
8
+ const previousHref = useRef(href);
9
+ const name = auth ? displayName(auth) : "";
10
+ const userId = auth?.user?.id;
11
+ useEffect(() => {
12
+ if (previousHref.current !== href) {
13
+ previousHref.current = href;
14
+ setGreeting(null);
15
+ }
16
+ }, [href]);
17
+ useEffect(() => {
18
+ if (!userId || auth?.loading) {
19
+ setGreeting(null);
20
+ return;
21
+ }
22
+ const greet = () => {
23
+ const result = amsterdamGreeting(new Date(), name);
24
+ if (claimGreeting(userId, result.date, () => window.localStorage, seen)) {
25
+ setGreeting({ userId, text: result.text });
26
+ }
27
+ else {
28
+ // Profile data may arrive after session metadata; refresh only an
29
+ // already visible greeting, never resurrect one hidden by navigation.
30
+ setGreeting((current) => current?.userId === userId ? { userId, text: result.text } : current);
31
+ }
32
+ };
33
+ greet();
34
+ // Also cover an open tab crossing Amsterdam midnight.
35
+ const timer = window.setInterval(greet, 60000);
36
+ window.addEventListener("focus", greet);
37
+ return () => { window.clearInterval(timer); window.removeEventListener("focus", greet); };
38
+ }, [userId, auth?.loading, name]);
39
+ return greeting?.userId === userId ? greeting?.text ?? null : null;
40
+ }
@@ -6,7 +6,7 @@ declare const Command: React.ForwardRefExoticComponent<Omit<{
6
6
  ref?: React.Ref<HTMLDivElement>;
7
7
  } & {
8
8
  asChild?: boolean;
9
- }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
9
+ }, "key" | "asChild" | keyof React.HTMLAttributes<HTMLDivElement>> & {
10
10
  label?: string;
11
11
  shouldFilter?: boolean;
12
12
  filter?: (value: string, search: string, keywords?: string[]) => number;
@@ -22,7 +22,7 @@ declare const CommandInput: React.ForwardRefExoticComponent<Omit<Omit<Pick<Pick<
22
22
  ref?: React.Ref<HTMLInputElement>;
23
23
  } & {
24
24
  asChild?: boolean;
25
- }, "asChild" | "key" | keyof React.InputHTMLAttributes<HTMLInputElement>>, "type" | "value" | "onChange"> & {
25
+ }, "key" | "asChild" | keyof React.InputHTMLAttributes<HTMLInputElement>>, "type" | "value" | "onChange"> & {
26
26
  value?: string;
27
27
  onValueChange?: (search: string) => void;
28
28
  } & React.RefAttributes<HTMLInputElement>, "ref"> & React.RefAttributes<HTMLInputElement>>;
@@ -32,7 +32,7 @@ declare const CommandList: React.ForwardRefExoticComponent<Omit<{
32
32
  ref?: React.Ref<HTMLDivElement>;
33
33
  } & {
34
34
  asChild?: boolean;
35
- }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
35
+ }, "key" | "asChild" | keyof React.HTMLAttributes<HTMLDivElement>> & {
36
36
  label?: string;
37
37
  } & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
38
38
  declare const CommandEmpty: React.ForwardRefExoticComponent<Omit<{
@@ -41,14 +41,14 @@ declare const CommandEmpty: React.ForwardRefExoticComponent<Omit<{
41
41
  ref?: React.Ref<HTMLDivElement>;
42
42
  } & {
43
43
  asChild?: boolean;
44
- }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>> & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
44
+ }, "key" | "asChild" | keyof React.HTMLAttributes<HTMLDivElement>> & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
45
45
  declare const CommandGroup: React.ForwardRefExoticComponent<Omit<{
46
46
  children?: React.ReactNode;
47
47
  } & Omit<Pick<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
48
48
  ref?: React.Ref<HTMLDivElement>;
49
49
  } & {
50
50
  asChild?: boolean;
51
- }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>>, "value" | "heading"> & {
51
+ }, "key" | "asChild" | keyof React.HTMLAttributes<HTMLDivElement>>, "value" | "heading"> & {
52
52
  heading?: React.ReactNode;
53
53
  value?: string;
54
54
  forceMount?: boolean;
@@ -57,7 +57,7 @@ declare const CommandSeparator: React.ForwardRefExoticComponent<Omit<Pick<Pick<R
57
57
  ref?: React.Ref<HTMLDivElement>;
58
58
  } & {
59
59
  asChild?: boolean;
60
- }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>> & {
60
+ }, "key" | "asChild" | keyof React.HTMLAttributes<HTMLDivElement>> & {
61
61
  alwaysRender?: boolean;
62
62
  } & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
63
63
  declare const CommandItem: React.ForwardRefExoticComponent<Omit<{
@@ -66,7 +66,7 @@ declare const CommandItem: React.ForwardRefExoticComponent<Omit<{
66
66
  ref?: React.Ref<HTMLDivElement>;
67
67
  } & {
68
68
  asChild?: boolean;
69
- }, "asChild" | "key" | keyof React.HTMLAttributes<HTMLDivElement>>, "disabled" | "value" | "onSelect"> & {
69
+ }, "key" | "asChild" | keyof React.HTMLAttributes<HTMLDivElement>>, "disabled" | "value" | "onSelect"> & {
70
70
  disabled?: boolean;
71
71
  onSelect?: (value: string) => void;
72
72
  value?: string;
@@ -8,5 +8,5 @@ export { getRoleFlags } from "../auth/roles.js";
8
8
  export { handoverExchangeHandler, HANDOVER_MAX_BODY_BYTES, HANDOVER_MAX_CODE_LENGTH, } from "./handover-handler.js";
9
9
  export type { HandoverExchangeHandlerOptions } from "./handover-handler.js";
10
10
  export type { PermissionLevel } from "./permissions.server.js";
11
- export { getShellBaseUrl, applyShellBaseUrl, ensureCentralLink, runShellRuntime, normalizeShellBaseUrl, } from "./shell-runtime.js";
11
+ export { getShellBaseUrl, applyShellBaseUrl, ensureCentralLink, ensureCronKeys, checkCronRequest, runShellRuntime, normalizeShellBaseUrl, } from "./shell-runtime.js";
12
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACpG,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAClF,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,2BAA2B,EAC3B,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,8BAA8B,EAAE,MAAM,uBAAuB,CAAC;AAC5E,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAI/D,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,qBAAqB,GACtB,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACpG,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAClF,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EACL,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,2BAA2B,EAC3B,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAI7B,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,8BAA8B,EAAE,MAAM,uBAAuB,CAAC;AAC5E,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAI/D,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,qBAAqB,GACtB,MAAM,oBAAoB,CAAC"}
@@ -8,4 +8,4 @@ export { getRoleFlags } from "../auth/roles.js";
8
8
  export { handoverExchangeHandler, HANDOVER_MAX_BODY_BYTES, HANDOVER_MAX_CODE_LENGTH, } from "./handover-handler.js";
9
9
  // Shell-adres en Central-koppeling (alleen server; aangeroepen door de
10
10
  // door het dashboard gegenereerde globale request-middleware).
11
- export { getShellBaseUrl, applyShellBaseUrl, ensureCentralLink, runShellRuntime, normalizeShellBaseUrl, } from "./shell-runtime.js";
11
+ export { getShellBaseUrl, applyShellBaseUrl, ensureCentralLink, ensureCronKeys, checkCronRequest, runShellRuntime, normalizeShellBaseUrl, } from "./shell-runtime.js";
@@ -13,12 +13,18 @@ export declare function getShellBaseUrl(): Promise<string | null>;
13
13
  */
14
14
  export declare function applyShellBaseUrl(): Promise<string | null>;
15
15
  /**
16
- * Hooguit eens per uur per isolate. Netwerkfout of ontbrekende RPC: niets
17
- * doen, waarschuwen, niet opnieuw tot het volgende uur.
16
+ * Na succes één uur wachten per isolate; na een fout vijf minuten.
18
17
  */
19
18
  export declare function ensureCentralLink(): Promise<void>;
20
- /** Voor de globale request-middleware van de shell. Gooit nooit. */
21
- export declare function runShellRuntime(): Promise<void>;
19
+ /** Synchroniseert de shell-publishable key; uur na succes, vijf minuten na fout. */
20
+ export declare function ensureCronKeys(): Promise<void>;
21
+ /** Alleen geregistreerde cronpaden; alle lees-/runtimefouten laten door. */
22
+ export declare function checkCronRequest(request: Request): Promise<Response | null>;
23
+ /** Zonder context hetzelfde gedrag als voorheen. Alleen bewuste cronweigering geeft 401. */
24
+ export declare function runShellRuntime(ctx?: {
25
+ request: Request;
26
+ pathname: string;
27
+ }): Promise<Response | null>;
22
28
  /** Alleen voor tests. */
23
29
  export declare function __resetShellRuntimeForTests(): void;
24
30
  //# sourceMappingURL=shell-runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shell-runtime.d.ts","sourceRoot":"","sources":["../../src/core/server/shell-runtime.ts"],"names":[],"mappings":"AAwDA,iDAAiD;AACjD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CASjE;AAmBD;;;;GAIG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqB9D;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAYhE;AAyDD;;;GAGG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAevD;AAED,oEAAoE;AACpE,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAMrD;AAED,yBAAyB;AACzB,wBAAgB,2BAA2B,SAM1C"}
1
+ {"version":3,"file":"shell-runtime.d.ts","sourceRoot":"","sources":["../../src/core/server/shell-runtime.ts"],"names":[],"mappings":"AAmHA,iDAAiD;AACjD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CASjE;AAmBD;;;;GAIG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmB9D;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAYhE;AA+DD;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAcvD;AA4BD,oFAAoF;AACpF,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAWpD;AAyFD,4EAA4E;AAC5E,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAuBjF;AAED,4FAA4F;AAC5F,wBAAsB,eAAe,CAAC,GAAG,CAAC,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAU5G;AAED,yBAAyB;AACzB,wBAAgB,2BAA2B,SAW1C"}