commit 18b7825592784f384c454da0c72d6607c9404c18 Author: Alex Dunmow Date: Sun Jul 5 03:04:18 2026 +0800 Extract calcomblock into a standalone wasm plugin repo Convert the bundled backend/internal/plugins/calcomblock package into a standalone reactor-mode wasm plugin, dropping every git.dev.alexdunmow.com/block/cms import (forbidden in standalone plugins): - package calcomblock -> package main; add wasip1 main.go with wasmguest.Serve(Registration). go.mod module git.dev.alexdunmow.com/block/calcomblock, block/core v0.18.2, no replace directives. - Settings persistence: replace the pool-backed poolQuerier (direct SQL against the public-schema `settings` table, which a sandboxed plugin role cannot read) with capabilityQuerier over the SDK settings.Settings / settings.Updater capabilities. GetSiteTimezone now resolves via GetSiteSettings host-side. - Vendor the small CMS-internal helpers the plugin used into internal/helpers (GetRealIP, StartCleanupLoop, MaskSecret, GetStringOr, the PluginSettingsQuerier settings helpers, PluginCrypto for tests) and internal/db (minimal Setting / UpsertSettingParams), each with a provenance header. - Inline the captcha widget: vendor blocks.CaptchaWidget as a local CaptchaWidget templ (captcha_widget.templ) and regenerate templ; drop the block/cms/blocks import from booking.templ. - blockConfig (server-authoritative captcha requirement via a page_block_snapshots scan) has no wasm-ABI capability, so it is left nil: honeypot + per-IP rate limit still apply. Documented as a follow-up. - web: @block-ninja/ui workspace:* -> ^0.1.0 registry version; eslint.config.js repointed at ../../../cms/web/eslint.config.js; rebuild web/dist. - Rewrite CLAUDE.md for the standalone wasm reality; add .gitignore. Full test suite preserved and passing; builds to calcomblock-2.0.0.bnp; check-safety passes. Co-Authored-By: Claude Fable 5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03e10b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.bnp +*.wasm +/web/node_modules/ +/web/pnpm-workspace.yaml diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..bbf9755 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,52 @@ +# calcomblock plugin + +Standalone **wasm** Cal.com Booking plugin (reactor-mode wasip1, packed into a +`.bnp` and hot-loaded by the CMS). Registers the `calcom:booking` block plus a +chi HTTP handler mounted at `/api/plugins/calcomblock/` (public booking + slots ++ webhook endpoints, admin-gated settings/test/event-types). The block editor is +a Vite / Module-Federation bundle in `web/` (exposed as `remoteEntry.js`), +served host-side from the `.bnp` at `/plugins/calcomblock/`. + +Extracted from the former bundled `cms/backend/internal/plugins/calcomblock`. +The old `.so`/Air/`go:embed` workflow is gone — see `~/src/blockninja/plugins/AGENTS.md` +and the `developing-blockninja-plugins` skill for the current build/publish loop. + +## SDK boundary + +Only `git.dev.alexdunmow.com/block/core/...` is importable — never `block/cms/...`. +Two small things that used to come from `cms/internal` are vendored under +`internal/` with provenance headers: + +- `internal/helpers` — `GetRealIP`, `StartCleanupLoop`, `MaskSecret`, + `GetStringOr`, the `PluginSettingsQuerier` settings helpers, and a + `PluginCrypto` (AES-256-GCM) used only by tests. +- `internal/db` — the minimal `Setting` / `UpsertSettingParams` structs the + settings helpers reference. + +## Settings persistence (wasm sandbox) + +Settings go through the SDK settings capabilities, NOT direct SQL: a plugin's +Postgres role is sandboxed to its own schema, so the CMS `settings` table +(public schema) is unreachable. `capabilityQuerier` (settings_store.go) adapts +`settings.Settings` / `settings.Updater` to the `PluginSettingsQuerier` +interface and is wired in `NewCalcomRouter`. + +## Known limitation + +The server-authoritative captcha requirement (`CalcomBookingRequiresCaptcha`, +which scanned published `page_block_snapshots`) has no wasm-ABI capability +equivalent, so `blockConfig` is nil: an enabled booking block still enforces the +honeypot + per-IP rate limit, but the published-content captcha gate is not +re-derived server-side. Restoring it needs a core capability exposing +published-block content across the ABI. + +## Frontend + +```bash +cd web && node node_modules/vite/bin/vite.js build # rebuild dist (pnpm's run-wrapper + # trips on ignored build scripts) +``` + +`web/dist` IS committed and packed into the `.bnp`; rebuild it before +`ninja plugin build`. `@block-ninja/ui` installs from the scoped Gitea registry +(`^0.1.0`), not a workspace link. diff --git a/block.go b/block.go new file mode 100644 index 0000000..b80f8e4 --- /dev/null +++ b/block.go @@ -0,0 +1,230 @@ +package main + +import ( + "bytes" + "context" + "time" + + "git.dev.alexdunmow.com/block/core/blocks" + "git.dev.alexdunmow.com/block/core/settings" +) + +// renderSettings is the SettingsManager available to CalcomBookingFunc, which +// renders during the public page pipeline where no HTTP request (and thus no +// visitor timezone) exists. Set once by NewCalcomRouter when the plugin's HTTP +// routes are mounted at startup — before any page render. Nil in editor-only +// or test contexts, where the grid falls back to the server clock. +var renderSettings *SettingsManager + +// BookingConfig holds the configuration for rendering the booking widget +type BookingConfig struct { + BlockID string + Username string + EventTypeSlug string + Title string + Description string + ShowTimezone bool + WeeksToShow int + WeekStart string // YYYY-MM-DD — first date in the current grid window + Today string // YYYY-MM-DD — today in the resolved timezone, for past-clamping + TimeZone string // IANA name shown in the "All times shown in" label + RangeLabel string // e.g. "May 27 – Jun 10, 2026" — populated by handler + CanGoBack bool // false when the window starts today (no point going earlier) + Dates []DateCell + // UnavailableMessage is the admin-authored copy shown when an online booking + // can't be completed for a non-fixable reason (Tier-2). Emitted as a hidden + // form input so the POST /book handler can read it back; defaults to + // DefaultUnavailableMessage when the admin leaves it blank. + UnavailableMessage string +} + +// DateCell represents a single date in the calendar grid +type DateCell struct { + Date string // YYYY-MM-DD format + DayOfMonth string // e.g., "15" + DayOfWeek string // e.g., "Mon" + IsToday bool + IsPast bool + IsSelected bool // server-rendered selected state — set by HandleGetSlots +} + +// CalcomBookingFunc renders the Cal.com booking widget +func CalcomBookingFunc(ctx context.Context, content map[string]any) string { + // Extract block ID from context or content + blockID := settings.GetStringOr(content, "_block_id", "") + if blockID == "" { + // Generate a simple ID if not provided + blockID = "calcom-" + time.Now().Format("150405") + } + + config := BookingConfig{ + BlockID: blockID, + Username: settings.GetStringOr(content, "username", ""), + EventTypeSlug: settings.GetStringOr(content, "eventTypeSlug", ""), + Title: settings.GetStringOr(content, "title", ""), + Description: settings.GetStringOr(content, "description", ""), + ShowTimezone: settings.GetBoolOr(content, "showTimezone", true), + WeeksToShow: settings.GetIntOr(content, "weeksToShow", 2), + UnavailableMessage: settings.GetStringOr(content, "unavailableMessage", DefaultUnavailableMessage), + } + + // Check for missing required configuration + if config.Username == "" || config.EventTypeSlug == "" { + if blocks.IsEditor(ctx) { + return renderEditorPlaceholder() + } + return renderConfigError() + } + + // Generate date grid for the calendar — initial window starts today on + // the MEETING's calendar: the cached event timezone when warm (filled by + // the first HTMX hit against the plugin endpoints), else the site + // Localization setting. Page render never blocks on a Cal.com round-trip. + // Without this, a container running UTC starts the grid on yesterday's + // date for a Perth schedule until 08:00 AWST. + loc, tzName := time.UTC, "UTC" + if renderSettings != nil { + loc, tzName = renderSettings.resolveLocation(ctx, peekEventTZ(config.Username, config.EventTypeSlug)) + } + now := time.Now().In(loc) + today := now.Format("2006-01-02") + config.WeekStart = today + config.Today = today + config.TimeZone = tzName + config.Dates = generateDateGridFrom(now, config.WeeksToShow, today) + config.RangeLabel = formatRangeLabel(now, config.WeeksToShow) + config.CanGoBack = false // initial window is already at today + + // Render the booking widget + var buf bytes.Buffer + if err := CalcomBookingWidget(config).Render(ctx, &buf); err != nil { + return renderError("Failed to render booking widget: " + err.Error()) + } + + return buf.String() +} + +// formatRangeLabel renders the human-readable label for the date grid header, +// e.g. "May 27 – Jun 10, 2026". If start and end fall in the same month, the +// label collapses to "May 27 – 31, 2026". +func formatRangeLabel(start time.Time, weeks int) string { + if weeks <= 0 { + weeks = 1 + } + end := start.AddDate(0, 0, weeks*7-1) + if start.Year() == end.Year() && start.Month() == end.Month() { + return start.Format("Jan 2") + " – " + end.Format("2, 2006") + } + if start.Year() == end.Year() { + return start.Format("Jan 2") + " – " + end.Format("Jan 2, 2006") + } + return start.Format("Jan 2, 2006") + " – " + end.Format("Jan 2, 2006") +} + +// prevWeekStart returns the YYYY-MM-DD string for the previous date-grid +// window — weeks*7 days before the current weekStart, but clamped to today +// (the visitor can't book in the past). Used by the date-grid prev button. +// today is the YYYY-MM-DD date in the resolved timezone (BookingConfig.Today) +// — not the server clock's, which can be a day behind the visitor's. +func prevWeekStart(currentStart string, weeks int, today string) string { + if weeks <= 0 { + weeks = 1 + } + t, err := time.Parse("2006-01-02", currentStart) + if err != nil || today > currentStart { + return today + } + prev := t.AddDate(0, 0, -weeks*7) + if prev.Format("2006-01-02") < today { + return today + } + return prev.Format("2006-01-02") +} + +// nextWeekStart returns the YYYY-MM-DD string for the next date-grid window — +// weeks*7 days after the current weekStart. +func nextWeekStart(currentStart string, weeks int) string { + if weeks <= 0 { + weeks = 1 + } + t, err := time.Parse("2006-01-02", currentStart) + if err != nil { + t = time.Now() + } + return t.AddDate(0, 0, weeks*7).Format("2006-01-02") +} + +// generateDateGridFrom produces `weeks*7` consecutive day cells starting at +// `start`. Days strictly before `today` (YYYY-MM-DD in the resolved timezone) +// are flagged IsPast so the templ can render them disabled. +func generateDateGridFrom(start time.Time, weeks int, today string) []DateCell { + if weeks <= 0 { + weeks = 1 + } + dates := make([]DateCell, 0, weeks*7) + for i := 0; i < weeks*7; i++ { + date := start.AddDate(0, 0, i) + dateStr := date.Format("2006-01-02") + dates = append(dates, DateCell{ + Date: dateStr, + DayOfMonth: date.Format("2"), + DayOfWeek: date.Format("Mon"), + IsToday: dateStr == today, + IsPast: dateStr < today, + }) + } + return dates +} + +// leadingBlanks returns how many empty spacer cells must precede the first +// date so it lands under its weekday column header in the 7-column grid +// (Sun = column 1). Without these, a window starting mid-week pours into +// column 1 and every date renders under the wrong weekday — the bidbuddy +// "tapped Tue, got Friday" incident. +func leadingBlanks(dates []DateCell) int { + if len(dates) == 0 { + return 0 + } + t, err := time.Parse("2006-01-02", dates[0].Date) + if err != nil { + return 0 + } + return int(t.Weekday()) +} + +func renderEditorPlaceholder() string { + return `
+ + + +

Cal.com Booking

+

Select an event type in the editor

+
` +} + +func renderConfigError() string { + return `
+

Cal.com booking block is not configured properly.

+
` +} + +func renderError(msg string) string { + return `
+

` + msg + `

+
` +} + +// inputTypeFor maps a Cal.com bookingField type to an HTML value. +// Unknown types fall through to `text`. +func inputTypeFor(t string) string { + switch t { + case "email": + return "email" + case "phone": + return "tel" + case "number": + return "number" + default: + return "text" + } +} diff --git a/booking.templ b/booking.templ new file mode 100644 index 0000000..3358976 --- /dev/null +++ b/booking.templ @@ -0,0 +1,922 @@ +package main + +import ( + "fmt" + "strconv" +) + +// CalcomBookingWidget renders the main booking widget container. +// +// The widget is divided into four regions, each with a stable ID so HTMX can +// swap them independently: +// +// - #calcom-steps-{id} step indicator (OOB-updated on every transition) +// - #calcom-date-grid-{id} the date picker +// - #calcom-slots-{id} time slots, loaded after a date click +// - #calcom-form-{id} booking form, loaded after a slot click +// - #calcom-confirm-{id} success panel, populated after submit +// +// Each partial returned by the server emits hx-swap-oob directives to keep +// the step indicator in sync with the user's progress, and to clear stale +// regions when the user changes their mind (Change date / Change time) or +// finishes (post-confirmation). +templ CalcomBookingWidget(config BookingConfig) { +
+ // Booking times render server-side in the MEETING's timezone (the + // event's Cal.com availability schedule) — the visitor's browser + // timezone is deliberately never detected or sent. Client-side + // timezone plumbing is what caused the 2026-06-10 incident: a hidden + // input's "UTC" default never got updated, every hx-include sent + // timezone=UTC, and visitors saw UTC slot times. + // + // This script only: + // - captures the browser locale for phone dial-code inference + // - wires the date-grid selection highlight + // - drives the phone country combobox + // + // Init is deferred to DOMContentLoaded: the locale input sits after + // this script in the widget and doesn't exist while it parses. + + + // Admin-authored fallback copy for a non-fixable booking failure + // (Tier-2). Stamped once from block content here and hx-included into + // the POST /book request (like locale) so HandleCreateBooking can read + // it back. templ auto-escapes the value. Defaults to + // DefaultUnavailableMessage when the admin leaves it blank (resolved in + // block.go), so it is never empty. + + if config.Title != "" { +

{ config.Title }

+ } + if config.Description != "" { +

{ config.Description }

+ } + // Step indicator — wrapped in a stable container so partial responses + // can replace its contents via hx-swap-oob. +
+ @calcomStepIndicator(1) +
+ if config.ShowTimezone { +
+ + All times shown in + { config.TimeZone } +
+ } + // Saved-booking panel — normally hidden. The widget script reveals it on + // DOMContentLoaded when this visitor's localStorage holds a booking for + // this username/event, hiding the date grid below so they manage the + // existing booking (cancel / redo) instead of double-booking. + @calcomSavedBooking(config.BlockID) + // Date picker section — wrapped so confirmation can OOB-clear it. +
+ @calcomDateGrid(config) +
+ // Time slots section — populated after a date click. +
+ // Booking form section — populated after a slot click. +
+ // Confirmation section — populated after successful booking. +
+ // Central loading indicator shown during HTMX requests. +
+ +
+
+} + +// calcomStepIndicator renders the three-step progress indicator. `active` is +// the user's current step (1 = Select Date, 2 = Select Time, 3 = Confirm, +// 4 = Done — all three checkmarked). When active > n the step is rendered as +// completed; when active == n it is current; when active < n it is upcoming. +templ calcomStepIndicator(active int) { +
+ @calcomStep(1, "Select Date", active) +
+ @calcomStep(2, "Select Time", active) +
+ @calcomStep(3, "Confirm", active) +
+} + +// calcomStep renders one step pill inside the progress indicator. +templ calcomStep(n int, label string, active int) { +
+ = n), + templ.KV("bg-muted text-muted-foreground", active < n), + }> + if active > n { + + } else { + { strconv.Itoa(n) } + } + + { label } +
+} + +// calcomStepIndicatorOOB wraps calcomStepIndicator in the OOB-swap envelope +// so partial responses can update the indicator out-of-band. +templ calcomStepIndicatorOOB(blockID string, active int) { +
+ @calcomStepIndicator(active) +
+} + +// calcomClearRegion emits an empty container with the given ID and an OOB +// swap directive, used to wipe stale regions when the user transitions +// between flow steps (Change date / Change time / post-confirmation). +templ calcomClearRegion(id, extraClasses string) { +
+} + +// calcomDateGrid renders the calendar date picker. The prev/next buttons fire +// hx-get against /api/plugins/calcomblock/date-grid, which returns a fresh +// instance of this template scoped to a new WeekStart — innerHTML-swapped +// into the #calcom-date-grid-{id} container in the widget root. +templ calcomDateGrid(config BookingConfig) { +
+
+ if config.CanGoBack { + + } else { + + } + { config.RangeLabel } + +
+
+ for _, day := range []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} { +
+ { day } +
+ } +
+
+ // Offset the first date to its weekday column. Without these pad + // cells a mid-week window pours in at the "Sun" column and every + // date renders under the wrong header — the second bidbuddy + // incident (2026-06-10): tapped the cell under "Tue", got Friday. + for i, pad := 0, leadingBlanks(config.Dates); i < pad; i++ { + + } + for _, date := range config.Dates { + if date.IsPast { + + } else { + + } + } +
+
+} + +// CalcomTimeSlots renders available time slots for a selected date. Returned +// as the body of GET /slots; also emits OOB updates for the step indicator +// (active=2) and an OOB clear of the form region so a previously-rendered +// form for an earlier slot doesn't linger. +templ CalcomTimeSlots(slots []TimeSlot, config SlotConfig) { +
+
+

+ Available times for { config.SelectedDate } +

+ +
+ if len(slots) == 0 { +

No available times for this date. Please select another date.

+ } else { +
+ for _, slot := range slots { + + } +
+ } +
+ // OOB: advance step indicator to "Select Time" + clear any stale form + // from a previous slot selection. + @calcomStepIndicatorOOB(config.BlockID, 2) + @calcomClearRegion(fmt.Sprintf("calcom-form-%s", config.BlockID), "calcom-booking-form") +} + +// CalcomBookingForm renders the contact form for booking. When the event type +// has custom bookingFields configured, each is rendered via bookingField. When +// the list is empty (typical for default event types or a transient fetch +// error), a built-in name + email pair is rendered so the flow still works. +// +// Returned as the body of GET /form. Also emits an OOB update advancing the +// step indicator to "Confirm". +templ CalcomBookingForm(formConfig FormConfig) { +
+
+
+

Your Details

+

+ Booking for { formConfig.SelectedDate } at { formConfig.SelectedTime } +

+
+ +
+ // hx-include pulls the widget-level hidden inputs into the POST: locale + // (phone dial-code inference) and unavailableMessage (admin-authored + // Tier-2 copy). Both live in the widget root, outside this form partial. +
+ + + + + // Honeypot: a real user can't see this off-screen input; bots and + // over-eager autofill heuristics still hit it. The name is + // deliberately non-semantic so Chrome's autofill doesn't fire on + // "company" / "address" / "website". + + if len(formConfig.BookingFields) > 0 { + for _, f := range formConfig.BookingFields { + @bookingField(f, formConfig.BlockID, formConfig.DefaultCountryCode) + } + } else { + @bookingField(BookingField{Name: "name", Label: "Name", Type: "name", Required: true, Placeholder: "Your name"}, formConfig.BlockID, "") + @bookingField(BookingField{Name: "email", Label: "Email", Type: "email", Required: true, Placeholder: "your@email.com"}, formConfig.BlockID, "") + } + // Cap proof-of-work widget — rendered only when the block enables + // captcha. On solve it injects a hidden cap-token field into this form, + // which HandleCreateBooking verifies (server-authoritative: same flag). + if formConfig.CaptchaEnabled { +
+ @CaptchaWidget() +
+ } + +
+
+ // OOB: advance step indicator to "Confirm". + @calcomStepIndicatorOOB(formConfig.BlockID, 3) +} + +// bookingField renders a single Cal.com bookingField in the correct widget for +// its type. Unknown types fall back to a text input. defaultCountry is the ISO +// region pre-selected in the phone-field country combobox ("" = none). +templ bookingField(f BookingField, blockID string, defaultCountry string) { +
+ + switch f.Type { + case "textarea": + + case "boolean", "checkbox": +
+ + { f.Placeholder } +
+ case "select": + + case "multiselect": +
+ for _, opt := range f.Options { + + } +
+ case "radio": +
+ for _, opt := range f.Options { + + } +
+ case "phone": +
+
+ + + +
+ +
+ default: + + } +
+} + +// CalcomConfirmation renders the success panel after a booking is created. +// Returned as the body of POST /book; also emits OOB clears that wipe the +// date grid + slots + form so the visitor is presented with only the +// confirmation (preventing accidental double-submits) and an OOB step-indicator +// update marking all three steps complete. +templ CalcomConfirmation(result BookingResult, blockID string) { +
+
+ +
+

Booking Confirmed!

+

+ Your appointment has been scheduled for { result.FormattedDateTime }. +

+

+ A confirmation email has been sent to { result.Email }. +

+ if result.MeetingLink != "" { + + Join meeting + + + } + // Machine-readable booking identity for the "manage your booking" feature. + // Hidden from view; the script below reads it and persists the booking to + // this visitor's localStorage so they can cancel / redo it on a later + // visit. templ auto-escapes every attribute value. + + // Persist {uid, when, email, savedAt} under a STABLE key derived from the + // widget root's data-username + data-event-type (NOT the per-render + // blockId). HTMX evaluates this script when it swaps the confirmation in. + // Skips when uid is empty — the honeypot path renders a fake confirmation + // with no UID, which must never poison the saved-booking panel. + +
+ // OOB: mark all three steps complete and clear the upstream regions so + // the visitor cannot re-submit the form (prevents the double-booking + // footgun the adversarial review caught). + @calcomStepIndicatorOOB(blockID, 4) + @calcomClearRegion(fmt.Sprintf("calcom-date-grid-%s", blockID), "calcom-date-picker") + @calcomClearRegion(fmt.Sprintf("calcom-slots-%s", blockID), "calcom-time-slots mt-6") + @calcomClearRegion(fmt.Sprintf("calcom-form-%s", blockID), "calcom-booking-form") +} + +// calcomSavedBooking renders the normally-hidden "manage your booking" panel. +// The widget script un-hides it (and hides the date grid) on DOMContentLoaded +// when this visitor's localStorage holds a booking for this username/event, +// filling in the date/time + email from storage. It offers two actions: +// +// - Cancel: an HTMX form POSTing /cancel with a hidden `uid` the script +// populates from localStorage; on a successful swap the script clears the +// storage key (htmx:afterRequest listener in the widget script). +// - Reschedule (redo): cancels the existing booking then reveals the grid to +// rebook — wired in the widget script to trigger the same Cancel POST, so +// the visitor can't end up double-booked. +// +// Cancellation is authorized by the Cal.com booking UID alone (the same +// capability model as Cal.com's emailed cancel link); the server rate-limits +// the POST by IP as the abuse guard. +templ calcomSavedBooking(blockID string) { + +} + +// CalcomCancelled is the body returned by POST /cancel on success. It swaps a +// "your booking was cancelled" message into #calcom-confirm-{id} and rewinds +// the step indicator to step 1. The date grid is restored client-side: when +// cancel fires from the saved panel the grid is only CSS-hidden (never removed), +// so the widget script's htmx:afterRequest listener re-shows it (showGrid) — +// avoiding a server-side grid re-render that would need the booking config the +// cancel POST doesn't carry. That same listener also clears the saved booking +// from localStorage and hides the saved panel. +templ CalcomCancelled(blockID string) { +
+
+ +
+

Booking cancelled

+

+ Your booking has been cancelled. You can book another time below whenever you're ready. +

+
+ // OOB: rewind the step indicator to step 1 (the grid is re-shown client-side). + @calcomStepIndicatorOOB(blockID, 1) +} + +// CalcomReset is the body returned by GET /reset — empty slots region with +// an OOB clear of the form (so the user pressing "Change date" loses any +// stale form rendered from an earlier slot click) and an OOB rewind of the +// step indicator to step 1. +templ CalcomReset(blockID string) { + // Main response body — empty, replaces #calcom-slots-{id}. + // OOB: clear the form region and rewind the step indicator. + @calcomClearRegion(fmt.Sprintf("calcom-form-%s", blockID), "calcom-booking-form") + @calcomStepIndicatorOOB(blockID, 1) +} + +// CalcomError renders an error message. +templ CalcomError(message string, blockID string, canRetry bool) { + +} diff --git a/booking_errors.go b/booking_errors.go new file mode 100644 index 0000000..b1161dd --- /dev/null +++ b/booking_errors.go @@ -0,0 +1,115 @@ +package main + +import ( + "errors" + "strings" +) + +// DefaultUnavailableMessage is the Tier-2 fallback shown when an online booking +// can't be completed for a reason the visitor can't fix (our auth/config, a +// Cal.com 5xx, a network timeout, or anything we don't recognise). It is the +// default both for the admin-authored block field (booking.templ / the editor's +// textarea default) AND for the handler's fallback when that field is left +// blank, so the two never drift. Admins can override it per block. +const DefaultUnavailableMessage = "Sorry — we couldn't complete your booking online. Please use our contact form and we'll get you booked in." + +// Tier-1 curated, user-actionable booking-error copy. Each names the actual +// problem the visitor can act on. The raw Cal.com message is NEVER surfaced — +// these strings are all the visitor ever sees. canRetry decides whether the +// error renders with the "Try again" reset control (true only when picking a +// different slot/time is the fix). +const ( + msgSlotUnavailable = "Sorry, that time isn't available anymore. Please choose another slot." + msgEventFullyBooked = "This event is fully booked right now. Please try a different time." + msgInvalidPhone = "That phone number doesn't look valid — please check it and try again." + msgInvalidGuestEmail = "One or more guest email addresses are invalid — please check them." + msgRejectedAnswer = "One of your answers wasn't accepted — please review the form and try again." +) + +// bookingErrorResult is the curated, visitor-facing outcome of classifying a +// CreateBooking failure: the message to render and whether to offer the +// retry/reset control. +type bookingErrorResult struct { + message string + canRetry bool +} + +// classifyBookingError maps a CreateBooking failure to curated visitor copy. +// +// Tier 1 (user-fixable) → a specific, actionable message, with canRetry=true +// where re-picking a slot/time is the fix. Tier 2 (our-side / non-fixable: +// auth/config 401-403, Cal.com 5xx, network/timeout, or anything unrecognised) +// → the admin-authored adminMessage (already defaulted to +// DefaultUnavailableMessage by the caller when blank). +// +// Cal.com's booking endpoint does NOT return a discriminating error.code — live +// probing (2026-06-21) showed every 400 carrying a generic "BadRequestException" +// or "BAD_REQUEST". The real signal is the message text, so classification is +// driven by case-insensitive substring matching on Status + Message, with Code +// only as a coarse gate. The raw Cal.com message never reaches the visitor. +func classifyBookingError(err error, adminMessage string) bookingErrorResult { + tier2 := bookingErrorResult{message: adminMessage, canRetry: false} + + var apiErr *CalcomAPIError + if !errors.As(err, &apiErr) { + // Network/transport error (DNS, timeout, connection reset) — no HTTP + // response came back. Not the visitor's to fix. + return tier2 + } + + // 401/403/5xx are our side (bad/expired API key, Cal.com outage). Anything + // outside the user-error band (HTTP 400 / 409 / 422) is Tier 2 too. + switch { + case apiErr.Status == 401 || apiErr.Status == 403: + return tier2 + case apiErr.Status >= 500: + return tier2 + case apiErr.Status != 400 && apiErr.Status != 409 && apiErr.Status != 422: + return tier2 + } + + msg := strings.ToLower(apiErr.Message) + switch { + // Slot gone: in the past, already taken, host no longer available. All + // recoverable by choosing another slot → render WITH the reset control. + case containsAny(msg, "in the past", "not available", "no longer available", + "already has booking", "already booked", "slot", "no_available_users", + "already_booked", "booking_seats_full_error", "host"): + return bookingErrorResult{message: msgSlotUnavailable, canRetry: true} + + // Event-level capacity exhausted (distinct copy; still time-recoverable). + case containsAny(msg, "booking_limit", "fully booked", "no more slots", + "limit reached", "exceeded", "sold out"): + return bookingErrorResult{message: msgEventFullyBooked, canRetry: true} + + // Bad attendee phone number (Cal.com: "{attendeePhoneNumber} invalid_number"). + case containsAny(msg, "invalid_number", "phone"): + return bookingErrorResult{message: msgInvalidPhone, canRetry: false} + + // Invalid guest email(s). Live probe: "responses - {guests}email_validation_error". + case strings.Contains(msg, "guest"): + return bookingErrorResult{message: msgInvalidGuestEmail, canRetry: false} + + // A rejected custom-field answer. "responses" / "bookingfields" signal a + // per-field validation rejection; render generic-but-actionable copy. + // (We do not echo Cal.com's field token — it isn't safe curated text.) + case containsAny(msg, "responses", "bookingfields", "booking field", + "required", "email_validation_error", "validation"): + return bookingErrorResult{message: msgRejectedAnswer, canRetry: false} + } + + // Recognised as a 400-class API error but no signal we can map to specific, + // safe copy → admin-authored message (never Cal.com's raw text). + return tier2 +} + +// containsAny reports whether s contains any of the given (already +// lower-cased) substrings. +func containsAny(s string, subs ...string) bool { + for _, sub := range subs { + if strings.Contains(s, sub) { + return true + } + } + return false +} diff --git a/booking_errors_test.go b/booking_errors_test.go new file mode 100644 index 0000000..24b46b7 --- /dev/null +++ b/booking_errors_test.go @@ -0,0 +1,120 @@ +package main + +import ( + "errors" + "fmt" + "testing" +) + +// TestClassifyBookingError_GroundedTaxonomy locks in the mapping against the +// REAL Cal.com error bodies captured by probing the live /book endpoint +// (2026-06-21). Each case feeds a *CalcomAPIError built from an actual response +// and asserts the curated visitor copy + retry affordance. The raw Cal.com +// message must never be the rendered string. +func TestClassifyBookingError_GroundedTaxonomy(t *testing.T) { + const adminMsg = "Call us on 1234." + + cases := []struct { + name string + err error + wantMsg string + wantRtry bool + }{ + { + // Live: status 400, BadRequestException: "Attempting to book a meeting in the past." + name: "past instant", + err: &CalcomAPIError{Endpoint: "booking", Status: 400, Code: "BadRequestException", Message: "Attempting to book a meeting in the past."}, + wantMsg: msgSlotUnavailable, + wantRtry: true, + }, + { + // Live: status 400, BadRequestException: "User either already has booking at this time or is not available" + name: "slot taken / host unavailable", + err: &CalcomAPIError{Endpoint: "booking", Status: 400, Code: "BadRequestException", Message: "User either already has booking at this time or is not available"}, + wantMsg: msgSlotUnavailable, + wantRtry: true, + }, + { + // Live: status 400, BAD_REQUEST: "responses - {guests}email_validation_error, " + name: "invalid guest email", + err: &CalcomAPIError{Endpoint: "booking", Status: 400, Code: "BAD_REQUEST", Message: "responses - {guests}email_validation_error, "}, + wantMsg: msgInvalidGuestEmail, + wantRtry: false, + }, + { + // Cal.com phone rejection: "{attendeePhoneNumber} invalid_number". + name: "invalid phone number", + err: &CalcomAPIError{Endpoint: "booking", Status: 400, Code: "BAD_REQUEST", Message: "responses - {attendeePhoneNumber}invalid_number"}, + wantMsg: msgInvalidPhone, + wantRtry: false, + }, + { + // A rejected custom-field answer (a required field / generic responses error + // that names no guest/phone signal). + name: "rejected custom field answer", + err: &CalcomAPIError{Endpoint: "booking", Status: 400, Code: "BAD_REQUEST", Message: "responses - {favourite-colour} is required"}, + wantMsg: msgRejectedAnswer, + wantRtry: false, + }, + { + name: "event fully booked / booking limit", + err: &CalcomAPIError{Endpoint: "booking", Status: 400, Code: "BadRequestException", Message: "booking_limit reached for this event type"}, + wantMsg: msgEventFullyBooked, + wantRtry: true, + }, + { + // Live: status 404, NotFoundException — unmapped → Tier-2 admin message. + name: "not found (unmapped 4xx) → admin message", + err: &CalcomAPIError{Endpoint: "booking", Status: 404, Code: "NotFoundException", Message: "Event type with slug nope not found"}, + wantMsg: adminMsg, + wantRtry: false, + }, + { + name: "auth failure (401) → admin message", + err: &CalcomAPIError{Endpoint: "booking", Status: 401, Code: "UnauthorizedException", Message: "Invalid API Key"}, + wantMsg: adminMsg, + wantRtry: false, + }, + { + name: "cal.com 5xx → admin message", + err: &CalcomAPIError{Endpoint: "booking", Status: 503, Code: "", Message: "service unavailable"}, + wantMsg: adminMsg, + wantRtry: false, + }, + { + name: "network/transport error (not a CalcomAPIError) → admin message", + err: fmt.Errorf("post bookings: %w", errors.New("dial tcp: i/o timeout")), + wantMsg: adminMsg, + wantRtry: false, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := classifyBookingError(tc.err, adminMsg) + if got.message != tc.wantMsg { + t.Errorf("message = %q, want %q", got.message, tc.wantMsg) + } + if got.canRetry != tc.wantRtry { + t.Errorf("canRetry = %v, want %v", got.canRetry, tc.wantRtry) + } + // The raw Cal.com message must never be the rendered copy. + var apiErr *CalcomAPIError + if errors.As(tc.err, &apiErr) && apiErr.Message != "" && got.message == apiErr.Message { + t.Errorf("rendered the raw Cal.com message verbatim: %q", got.message) + } + }) + } +} + +// TestClassifyBookingError_BlankAdminMessageIsCallerDefaulted documents that the +// classifier renders whatever adminMessage it is given for Tier-2 — the caller +// (HandleCreateBooking) substitutes DefaultUnavailableMessage when the form +// field is blank, so the two stay in sync via the single const. +func TestClassifyBookingError_BlankAdminMessageIsCallerDefaulted(t *testing.T) { + err := &CalcomAPIError{Endpoint: "booking", Status: 404, Message: "nope"} + got := classifyBookingError(err, DefaultUnavailableMessage) + if got.message != DefaultUnavailableMessage { + t.Errorf("message = %q, want DefaultUnavailableMessage", got.message) + } +} diff --git a/booking_templ.go b/booking_templ.go new file mode 100644 index 0000000..9d6ee21 --- /dev/null +++ b/booking_templ.go @@ -0,0 +1,2371 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +package main + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "strconv" +) + +// CalcomBookingWidget renders the main booking widget container. +// +// The widget is divided into four regions, each with a stable ID so HTMX can +// swap them independently: +// +// - #calcom-steps-{id} step indicator (OOB-updated on every transition) +// - #calcom-date-grid-{id} the date picker +// - #calcom-slots-{id} time slots, loaded after a date click +// - #calcom-form-{id} booking form, loaded after a slot click +// - #calcom-confirm-{id} success panel, populated after submit +// +// Each partial returned by the server emits hx-swap-oob directives to keep +// the step indicator in sync with the user's progress, and to clear stale +// regions when the user changes their mind (Change date / Change time) or +// finishes (post-confirmation). +func CalcomBookingWidget(config BookingConfig) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if config.Title != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(config.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `booking.templ`, Line: 223, Col: 69} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if config.Description != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(config.Description) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `booking.templ`, Line: 226, Col: 61} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = calcomStepIndicator(1).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if config.ShowTimezone { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
All times shown in ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(config.TimeZone) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `booking.templ`, Line: 240, Col: 63} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = calcomSavedBooking(config.BlockID).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = calcomDateGrid(config).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// calcomStepIndicator renders the three-step progress indicator. `active` is +// the user's current step (1 = Select Date, 2 = Select Time, 3 = Confirm, +// 4 = Done — all three checkmarked). When active > n the step is rendered as +// completed; when active == n it is current; when active < n it is upcoming. +func calcomStepIndicator(active int) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var15 := templ.GetChildren(ctx) + if templ_7745c5c3_Var15 == nil { + templ_7745c5c3_Var15 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = calcomStep(1, "Select Date", active).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = calcomStep(2, "Select Time", active).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = calcomStep(3, "Confirm", active).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// calcomStep renders one step pill inside the progress indicator. +func calcomStep(n int, label string, active int) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var16 := templ.GetChildren(ctx) + if templ_7745c5c3_Var16 == nil { + templ_7745c5c3_Var16 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + var templ_7745c5c3_Var17 = []any{"flex items-center gap-2", + templ.KV("opacity-50", active < n), + } + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var19 = []any{"flex items-center justify-center w-6 h-6 rounded-full text-xs font-medium shrink-0", + templ.KV("bg-primary text-primary-foreground", active >= n), + templ.KV("bg-muted text-muted-foreground", active < n), + } + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var19...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if active > n { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(n)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `booking.templ`, Line: 321, Col: 21} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var22 = []any{"hidden sm:inline", + templ.KV("text-foreground font-medium", active == n), + templ.KV("text-muted-foreground", active != n), + } + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var22...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var24 string + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `booking.templ`, Line: 328, Col: 11} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// calcomStepIndicatorOOB wraps calcomStepIndicator in the OOB-swap envelope +// so partial responses can update the indicator out-of-band. +func calcomStepIndicatorOOB(blockID string, active int) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var25 := templ.GetChildren(ctx) + if templ_7745c5c3_Var25 == nil { + templ_7745c5c3_Var25 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = calcomStepIndicator(active).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// calcomClearRegion emits an empty container with the given ID and an OOB +// swap directive, used to wipe stale regions when the user transitions +// between flow steps (Change date / Change time / post-confirmation). +func calcomClearRegion(id, extraClasses string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var27 := templ.GetChildren(ctx) + if templ_7745c5c3_Var27 == nil { + templ_7745c5c3_Var27 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + var templ_7745c5c3_Var28 = []any{extraClasses} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var28...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// calcomDateGrid renders the calendar date picker. The prev/next buttons fire +// hx-get against /api/plugins/calcomblock/date-grid, which returns a fresh +// instance of this template scoped to a new WeekStart — innerHTML-swapped +// into the #calcom-date-grid-{id} container in the widget root. +func calcomDateGrid(config BookingConfig) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var31 := templ.GetChildren(ctx) + if templ_7745c5c3_Var31 == nil { + templ_7745c5c3_Var31 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if config.CanGoBack { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var34 string + templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(config.RangeLabel) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `booking.templ`, Line: 369, Col: 91} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, day := range []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var37 string + templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(day) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `booking.templ`, Line: 385, Col: 10} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for i, pad := 0, leadingBlanks(config.Dates); i < pad; i++ { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + for _, date := range config.Dates { + if date.IsPast { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + var templ_7745c5c3_Var39 = []any{ // aspect-square + flex centring (not fixed p-3): cells track + // the column width, so a narrow phone container yields small + // squares instead of 25×52 stretched pills (iOS report, + // 2026-06-10 21:52). + "flex aspect-square w-full items-center justify-center rounded-lg font-medium transition-all", + "hover:bg-primary hover:text-primary-foreground", + "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + "aria-pressed:bg-primary aria-pressed:text-primary-foreground aria-pressed:ring-2 aria-pressed:ring-primary", + templ.KV("bg-primary/10 text-primary ring-1 ring-primary", date.IsToday && !date.IsSelected), + templ.KV("text-foreground", !date.IsToday && !date.IsSelected), + } + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var39...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// CalcomTimeSlots renders available time slots for a selected date. Returned +// as the body of GET /slots; also emits OOB updates for the step indicator +// (active=2) and an OOB clear of the form region so a previously-rendered +// form for an earlier slot doesn't linger. +func CalcomTimeSlots(slots []TimeSlot, config SlotConfig) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var46 := templ.GetChildren(ctx) + if templ_7745c5c3_Var46 == nil { + templ_7745c5c3_Var46 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "

Available times for ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var47 string + templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(config.SelectedDate) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `booking.templ`, Line: 447, Col: 45} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(slots) == 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "

No available times for this date. Please select another date.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, slot := range slots { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = calcomStepIndicatorOOB(config.BlockID, 2).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = calcomClearRegion(fmt.Sprintf("calcom-form-%s", config.BlockID), "calcom-booking-form").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// CalcomBookingForm renders the contact form for booking. When the event type +// has custom bookingFields configured, each is rendered via bookingField. When +// the list is empty (typical for default event types or a transient fetch +// error), a built-in name + email pair is rendered so the flow still works. +// +// Returned as the body of GET /form. Also emits an OOB update advancing the +// step indicator to "Confirm". +func CalcomBookingForm(formConfig FormConfig) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var54 := templ.GetChildren(ctx) + if templ_7745c5c3_Var54 == nil { + templ_7745c5c3_Var54 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "

Your Details

Booking for ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var57 string + templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinStringErrs(formConfig.SelectedDate) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `booking.templ`, Line: 499, Col: 42} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, " at ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var58 string + templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(formConfig.SelectedTime) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `booking.templ`, Line: 499, Col: 73} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(formConfig.BookingFields) > 0 { + for _, f := range formConfig.BookingFields { + templ_7745c5c3_Err = bookingField(f, formConfig.BlockID, formConfig.DefaultCountryCode).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } else { + templ_7745c5c3_Err = bookingField(BookingField{Name: "name", Label: "Name", Type: "name", Required: true, Placeholder: "Your name"}, formConfig.BlockID, "").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bookingField(BookingField{Name: "email", Label: "Email", Type: "email", Required: true, Placeholder: "your@email.com"}, formConfig.BlockID, "").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if formConfig.CaptchaEnabled { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = CaptchaWidget().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = calcomStepIndicatorOOB(formConfig.BlockID, 3).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// bookingField renders a single Cal.com bookingField in the correct widget for +// its type. Unknown types fall back to a text input. defaultCountry is the ISO +// region pre-selected in the phone-field country combobox ("" = none). +func bookingField(f BookingField, blockID string, defaultCountry string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var69 := templ.GetChildren(ctx) + if templ_7745c5c3_Var69 == nil { + templ_7745c5c3_Var69 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + switch f.Type { + case "textarea": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "boolean", "checkbox": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var78 string + templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.JoinStringErrs(f.Placeholder) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `booking.templ`, Line: 608, Col: 64} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var78)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "select": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "multiselect": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, opt := range f.Options { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "radio": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, opt := range f.Options { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "phone": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + default: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 169, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// CalcomConfirmation renders the success panel after a booking is created. +// Returned as the body of POST /book; also emits OOB clears that wipe the +// date grid + slots + form so the visitor is presented with only the +// confirmation (preventing accidental double-submits) and an OOB step-indicator +// update marking all three steps complete. +func CalcomConfirmation(result BookingResult, blockID string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var109 := templ.GetChildren(ctx) + if templ_7745c5c3_Var109 == nil { + templ_7745c5c3_Var109 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, "

Booking Confirmed!

Your appointment has been scheduled for ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var110 string + templ_7745c5c3_Var110, templ_7745c5c3_Err = templ.JoinStringErrs(result.FormattedDateTime) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `booking.templ`, Line: 732, Col: 69} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var110)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, ".

A confirmation email has been sent to ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var111 string + templ_7745c5c3_Var111, templ_7745c5c3_Err = templ.JoinStringErrs(result.Email) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `booking.templ`, Line: 735, Col: 81} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var111)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 172, ".

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if result.MeetingLink != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "Join meeting ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = calcomStepIndicatorOOB(blockID, 4).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = calcomClearRegion(fmt.Sprintf("calcom-date-grid-%s", blockID), "calcom-date-picker").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = calcomClearRegion(fmt.Sprintf("calcom-slots-%s", blockID), "calcom-time-slots mt-6").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = calcomClearRegion(fmt.Sprintf("calcom-form-%s", blockID), "calcom-booking-form").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// calcomSavedBooking renders the normally-hidden "manage your booking" panel. +// The widget script un-hides it (and hides the date grid) on DOMContentLoaded +// when this visitor's localStorage holds a booking for this username/event, +// filling in the date/time + email from storage. It offers two actions: +// +// - Cancel: an HTMX form POSTing /cancel with a hidden `uid` the script +// populates from localStorage; on a successful swap the script clears the +// storage key (htmx:afterRequest listener in the widget script). +// - Reschedule (redo): cancels the existing booking then reveals the grid to +// rebook — wired in the widget script to trigger the same Cancel POST, so +// the visitor can't end up double-booked. +// +// Cancellation is authorized by the Cal.com booking UID alone (the same +// capability model as Cal.com's emailed cancel link); the server rate-limits +// the POST by IP as the abuse guard. +func calcomSavedBooking(blockID string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var116 := templ.GetChildren(ctx) + if templ_7745c5c3_Var116 == nil { + templ_7745c5c3_Var116 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 179, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// CalcomCancelled is the body returned by POST /cancel on success. It swaps a +// "your booking was cancelled" message into #calcom-confirm-{id} and rewinds +// the step indicator to step 1. The date grid is restored client-side: when +// cancel fires from the saved panel the grid is only CSS-hidden (never removed), +// so the widget script's htmx:afterRequest listener re-shows it (showGrid) — +// avoiding a server-side grid re-render that would need the booking config the +// cancel POST doesn't carry. That same listener also clears the saved booking +// from localStorage and hides the saved panel. +func CalcomCancelled(blockID string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var121 := templ.GetChildren(ctx) + if templ_7745c5c3_Var121 == nil { + templ_7745c5c3_Var121 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 184, "

Booking cancelled

Your booking has been cancelled. You can book another time below whenever you're ready.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = calcomStepIndicatorOOB(blockID, 1).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// CalcomReset is the body returned by GET /reset — empty slots region with +// an OOB clear of the form (so the user pressing "Change date" loses any +// stale form rendered from an earlier slot click) and an OOB rewind of the +// step indicator to step 1. +func CalcomReset(blockID string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var122 := templ.GetChildren(ctx) + if templ_7745c5c3_Var122 == nil { + templ_7745c5c3_Var122 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = calcomClearRegion(fmt.Sprintf("calcom-form-%s", blockID), "calcom-booking-form").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = calcomStepIndicatorOOB(blockID, 1).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// CalcomError renders an error message. +func CalcomError(message string, blockID string, canRetry bool) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var123 := templ.GetChildren(ctx) + if templ_7745c5c3_Var123 == nil { + templ_7745c5c3_Var123 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 185, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var124 string + templ_7745c5c3_Var124, templ_7745c5c3_Err = templ.JoinStringErrs(message) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `booking.templ`, Line: 909, Col: 47} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var124)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 186, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if canRetry { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 187, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 190, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/captcha_widget.templ b/captcha_widget.templ new file mode 100644 index 0000000..23e6b89 --- /dev/null +++ b/captcha_widget.templ @@ -0,0 +1,36 @@ +package main + +// CaptchaWidget renders the self-hosted Cap (trycap.dev) proof-of-work widget. +// When placed inside a
, the widget injects a hidden `cap-token` field on +// solve, which the form handler verifies against the instance's redeem endpoint. +// +// Vendored from the CMS built-in blocks.CaptchaWidget (cms/backend/blocks/ +// captcha_widget.templ): a standalone wasm plugin cannot import the CMS-internal +// blocks package, but the widget markup is fully static (served from this +// instance's own origin), so it is reproduced here verbatim. Re-copy if the +// upstream widget changes. +templ CaptchaWidget() { + + + + +} diff --git a/captcha_widget_templ.go b/captcha_widget_templ.go new file mode 100644 index 0000000..96fcf11 --- /dev/null +++ b/captcha_widget_templ.go @@ -0,0 +1,49 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +package main + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +// CaptchaWidget renders the self-hosted Cap (trycap.dev) proof-of-work widget. +// When placed inside a , the widget injects a hidden `cap-token` field on +// solve, which the form handler verifies against the instance's redeem endpoint. +// +// Vendored from the CMS built-in blocks.CaptchaWidget (cms/backend/blocks/ +// captcha_widget.templ): a standalone wasm plugin cannot import the CMS-internal +// blocks package, but the widget markup is fully static (served from this +// instance's own origin), so it is reproduced here verbatim. Re-copy if the +// upstream widget changes. +func CaptchaWidget() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/client.go b/client.go new file mode 100644 index 0000000..e676f7b --- /dev/null +++ b/client.go @@ -0,0 +1,604 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + "unicode" +) + +// Cal.com v2 API constants. Each endpoint requires its own `cal-api-version` +// value — historically they have all diverged. +const ( + versionSlots = "2024-09-04" + versionEventTypes = "2024-06-14" + versionBookings = "2026-02-25" + versionMe = "2024-06-11" + versionSchedules = "2024-06-11" +) + +// calcomBaseURL is the Cal.com v2 API root. Declared as a var (not const) so +// the integration tests in handler_test.go can override it to point at a fake +// httptest.NewServer; in production it is set exactly once at startup and +// never mutated. The handlers call NewCalcomClient(apiKey) inline, which reads +// this value, so a single var swap reroutes every public handler at once. +var calcomBaseURL = "https://api.cal.com/v2" + +// CalcomClient is a thin wrapper over Cal.com's v2 REST API. Each method sets +// the version header appropriate for the endpoint it targets. +type CalcomClient struct { + http *http.Client + apiKey string + baseURL string +} + +// NewCalcomClient builds a client with the standard 30s HTTP timeout. +func NewCalcomClient(apiKey string) *CalcomClient { + return &CalcomClient{ + http: &http.Client{Timeout: 30 * time.Second}, + apiKey: apiKey, + baseURL: calcomBaseURL, + } +} + +// SlotsResponse mirrors the shape of `GET /v2/slots`: +// +// { "status": "success", "data": { "YYYY-MM-DD": [{ "time": "..." }] } } +type SlotsResponse struct { + Status string `json:"status"` + Data map[string][]SlotEntry `json:"data"` +} + +// SlotEntry is a single slot row in the SlotsResponse.Data map. +// +// The slot's timestamp field is cal-api-version dependent. The version this +// client pins — versionSlots = "2024-09-04" — returns {"start": ""}, +// whereas the older 2024-08-13 shape returned {"time": ""}. Cal.com +// has a long history of diverging response shapes across versions (see the +// version-const note above), so we decode BOTH fields and resolve the +// effective value via When() — keeping the widget working even if the pinned +// version is ever rolled forward or back. +type SlotEntry struct { + Start string `json:"start"` + Time string `json:"time"` +} + +// When returns the slot's RFC3339 start timestamp, preferring the 2024-09-04 +// `start` field and falling back to the legacy `time` field. +func (s SlotEntry) When() string { + if s.Start != "" { + return s.Start + } + return s.Time +} + +// GetSlots returns the slots Cal.com would offer for the given event type +// between [start, end] in the requested timezone. start/end should be RFC3339. +func (c *CalcomClient) GetSlots(ctx context.Context, username, eventTypeSlug, start, end, timeZone string) (SlotsResponse, error) { + q := url.Values{} + q.Set("username", username) + q.Set("eventTypeSlug", eventTypeSlug) + q.Set("start", start) + q.Set("end", end) + if timeZone != "" { + q.Set("timeZone", timeZone) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/slots?"+q.Encode(), nil) + if err != nil { + return SlotsResponse{}, err + } + req.Header.Set("Authorization", "Bearer "+c.apiKey) + req.Header.Set("cal-api-version", versionSlots) + + resp, err := c.http.Do(req) + if err != nil { + return SlotsResponse{}, err + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return SlotsResponse{}, statusError("slots", resp) + } + var out SlotsResponse + if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { + return SlotsResponse{}, fmt.Errorf("decode slots: %w", err) + } + return out, nil +} + +// ListEventTypes returns the compact summary list used to populate the editor +// dropdown. +func (c *CalcomClient) ListEventTypes(ctx context.Context, username string) ([]EventTypeSummary, error) { + q := url.Values{} + q.Set("username", username) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/event-types?"+q.Encode(), nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+c.apiKey) + req.Header.Set("cal-api-version", versionEventTypes) + + resp, err := c.http.Do(req) + if err != nil { + return nil, err + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return nil, statusError("event-types", resp) + } + var wrap struct { + Status string `json:"status"` + Data []EventTypeSummary `json:"data"` + } + if err := json.NewDecoder(resp.Body).Decode(&wrap); err != nil { + return nil, fmt.Errorf("decode event-types: %w", err) + } + return wrap.Data, nil +} + +// MeResponse is the subset of Cal.com's /v2/me response that the plugin uses +// to identify the API key's owner so the block editor doesn't have to ask the +// admin to type their own username. +type MeResponse struct { + Username string `json:"username"` + Email string `json:"email"` +} + +// GetMe returns the account identity associated with the configured API key. +// This is the source of truth for "whose Cal.com is this plugin configured +// for", and lets us drop the manual username input from the block editor. +func (c *CalcomClient) GetMe(ctx context.Context) (MeResponse, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/me", nil) + if err != nil { + return MeResponse{}, err + } + req.Header.Set("Authorization", "Bearer "+c.apiKey) + req.Header.Set("cal-api-version", versionMe) + + resp, err := c.http.Do(req) + if err != nil { + return MeResponse{}, err + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return MeResponse{}, statusError("me", resp) + } + var wrap struct { + Status string `json:"status"` + Data MeResponse `json:"data"` + } + if err := json.NewDecoder(resp.Body).Decode(&wrap); err != nil { + return MeResponse{}, fmt.Errorf("decode me: %w", err) + } + return wrap.Data, nil +} + +// ListSchedules returns the availability schedules of the account the API key +// belongs to. Used to resolve the meeting's timezone: an event type either +// pins a schedule (ScheduleID) or uses the account's default (IsDefault). +func (c *CalcomClient) ListSchedules(ctx context.Context) ([]Schedule, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/schedules", nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+c.apiKey) + req.Header.Set("cal-api-version", versionSchedules) + + resp, err := c.http.Do(req) + if err != nil { + return nil, err + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return nil, statusError("schedules", resp) + } + var wrap struct { + Status string `json:"status"` + Data []Schedule `json:"data"` + } + if err := json.NewDecoder(resp.Body).Decode(&wrap); err != nil { + return nil, fmt.Errorf("decode schedules: %w", err) + } + return wrap.Data, nil +} + +// GetEventType fetches the full event type record — including the configured +// `bookingFields` array — so the public form can render dynamic questions. +func (c *CalcomClient) GetEventType(ctx context.Context, username, eventTypeSlug string) (EventType, error) { + q := url.Values{} + q.Set("username", username) + q.Set("eventSlug", eventTypeSlug) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/event-types?"+q.Encode(), nil) + if err != nil { + return EventType{}, err + } + req.Header.Set("Authorization", "Bearer "+c.apiKey) + req.Header.Set("cal-api-version", versionEventTypes) + + resp, err := c.http.Do(req) + if err != nil { + return EventType{}, err + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return EventType{}, statusError("event-type", resp) + } + var wrap struct { + Status string `json:"status"` + Data []EventType `json:"data"` + } + if err := json.NewDecoder(resp.Body).Decode(&wrap); err != nil { + return EventType{}, fmt.Errorf("decode event-type: %w", err) + } + if len(wrap.Data) == 0 { + return EventType{}, fmt.Errorf("event type %q not found for user %q", eventTypeSlug, username) + } + et := wrap.Data[0] + et.BookingFields = normalizeBookingFields(et.BookingFields) + return et, nil +} + +// normalizeBookingFields adapts Cal.com's v2 bookingFields to the shape the +// booking form template renders. Without it, default/system fields decode with +// an empty Name (the v2 identifier is `slug`, not `name`) and an empty Label +// (Cal.com leaves system-field labels blank and renders them client-side from +// i18n) — producing unlabelled inputs with empty `name` attributes, which are +// both invisible to the visitor AND drop their values on submit. Hidden system +// fields (e.g. `title`, `rescheduleReason`) are dropped so they never render. +func normalizeBookingFields(fields []BookingField) []BookingField { + if len(fields) == 0 { + return fields + } + out := make([]BookingField, 0, len(fields)) + for _, f := range fields { + if f.Hidden || f.Editable == "system-but-hidden" { + continue + } + // Reschedule-only system fields (rescheduleReason carries + // views:[{id:"reschedule"}]) must never render on a NEW booking. + if isRescheduleOnlyView(f.Views) { + continue + } + // System fields the submit path drops because they have no valid + // new-booking home (location → structured top-level object handled by + // Cal.com's default; rescheduleReason → reschedule-only; title → + // system-managed). Drop them from the form too, so we never render an + // input whose value would be silently discarded. (rescheduleReason is + // also covered here by slug for event types that omit the views flag.) + if systemDropSlugs[f.Slug] || systemDropSlugs[f.Name] { + continue + } + if f.Name == "" { + f.Name = f.Slug + } + if f.Label == "" { + f.Label = bookingFieldLabel(f.Name) + } + out = append(out, f) + } + return out +} + +// isRescheduleOnlyView reports whether a booking field's `views` restrict it to +// the reschedule flow. Cal.com sets views:[{id:"reschedule"}] on rescheduleReason; +// such fields are invalid on a create booking and must not be rendered. +func isRescheduleOnlyView(views []BookingFieldView) bool { + if len(views) == 0 { + return false + } + for _, v := range views { + if v.ID != "reschedule" { + return false + } + } + return true +} + +// bookingFieldLabel resolves a human-readable label for a booking field whose +// Cal.com `label` came back empty — always the case for default/system fields. +// Known system slugs map to their conventional booker labels; anything else +// (a custom field that somehow lacks a label) is humanized from its slug. +func bookingFieldLabel(slug string) string { + switch slug { + case "name": + return "Name" + case "email": + return "Email" + case "location": + return "Location" + case "notes": + return "Additional notes" + case "guests": + return "Guests" + case "rescheduleReason": + return "Reason for reschedule" + case "attendeePhoneNumber", "phone", "smsReminderNumber": + return "Phone number" + case "title": + return "What is this meeting about?" + } + return humanizeSlug(slug) +} + +// humanizeSlug turns a field slug like "company-size" or "company_size" into a +// sentence-case display label ("Company size"). Empty input yields "Field" so a +// label element is never rendered completely blank. +func humanizeSlug(slug string) string { + s := strings.TrimSpace(strings.NewReplacer("-", " ", "_", " ").Replace(slug)) + if s == "" { + return "Field" + } + r := []rune(s) + r[0] = unicode.ToUpper(r[0]) + return string(r) +} + +// BookingAttendee carries the built-in fields that Cal.com expects every +// booking to include. Custom event-type fields travel in BookingFieldsResponses. +type BookingAttendee struct { + Name string `json:"name"` + Email string `json:"email"` + TimeZone string `json:"timeZone"` + Phone string `json:"phoneNumber,omitempty"` + Language string `json:"language,omitempty"` +} + +// BookingRequest is the v2 `POST /v2/bookings` body. The event type is +// identified by slug + username — no separate ID lookup. +type BookingRequest struct { + EventTypeSlug string `json:"eventTypeSlug"` + Username string `json:"username"` + Start string `json:"start"` + Attendee BookingAttendee `json:"attendee"` + BookingFieldsResponses map[string]any `json:"bookingFieldsResponses,omitempty"` + Metadata map[string]any `json:"metadata,omitempty"` + Guests []string `json:"guests,omitempty"` +} + +// BookingResponse is the parsed `POST /v2/bookings` success envelope. +type BookingResponse struct { + Status string `json:"status"` + Data BookingResponseData `json:"data"` +} + +// BookingResponseData carries the Cal.com-provided identifiers and the meeting +// location surfaced to the attendee. +// +// Per the 2026-02-25 POST /v2/bookings response, `location` is the canonical +// meeting field and `meetingUrl` is deprecated ("rely on 'location' instead"), +// kept here only as a fallback. The response does NOT carry rescheduleUrl / +// cancelUrl — those reach the attendee via Cal.com's confirmation email, so we +// no longer decode (or surface) them. +type BookingResponseData struct { + UID string `json:"uid"` + StartTime string `json:"start"` + EndTime string `json:"end"` + Location string `json:"location"` + MeetingURL string `json:"meetingUrl"` // deprecated by Cal.com; fallback only +} + +// MeetingLink returns the best available join URL. It prefers the canonical +// `location` field, but only when that is an http(s) URL — for non-video event +// types `location` can be a physical address or phone number, which must not be +// surfaced as a link. Falls back to the deprecated `meetingUrl`. +func (d BookingResponseData) MeetingLink() string { + if isHTTPURL(d.Location) { + return d.Location + } + if isHTTPURL(d.MeetingURL) { + return d.MeetingURL + } + return "" +} + +// isHTTPURL reports whether s is an http(s) URL. +func isHTTPURL(s string) bool { + return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://") +} + +// CreateBooking submits a new booking to Cal.com. For a regular or instant +// event type the response `data` is a single object; for a recurring event +// type it is an array (one entry per occurrence). Both shapes are resolved — +// a recurring booking surfaces its first occurrence. +func (c *CalcomClient) CreateBooking(ctx context.Context, br BookingRequest) (BookingResponse, error) { + body, err := json.Marshal(br) + if err != nil { + return BookingResponse{}, err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/bookings", bytes.NewReader(body)) + if err != nil { + return BookingResponse{}, err + } + req.Header.Set("Authorization", "Bearer "+c.apiKey) + req.Header.Set("cal-api-version", versionBookings) + req.Header.Set("Content-Type", "application/json") + + resp, err := c.http.Do(req) + if err != nil { + return BookingResponse{}, err + } + defer func() { _ = resp.Body.Close() }() + + respBody, _ := io.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + return BookingResponse{}, statusErrorWithBody("booking", resp.StatusCode, respBody) + } + // The bookings `data` is a single object for regular/instant bookings but an + // ARRAY for recurring event types. Decode the envelope with a raw `data` so + // both shapes resolve instead of hard-failing on the array form. + var env struct { + Status string `json:"status"` + Data json.RawMessage `json:"data"` + } + if err := json.Unmarshal(respBody, &env); err != nil { + return BookingResponse{}, fmt.Errorf("decode booking: %w", err) + } + out := BookingResponse{Status: env.Status} + switch trimmed := bytes.TrimSpace(env.Data); { + case len(trimmed) == 0: + // no data object — leave zero-value Data + case trimmed[0] == '[': + var arr []BookingResponseData + if err := json.Unmarshal(trimmed, &arr); err != nil { + return BookingResponse{}, fmt.Errorf("decode booking (recurring): %w", err) + } + if len(arr) == 0 { + return BookingResponse{}, fmt.Errorf("calcom booking: recurring data array was empty") + } + out.Data = arr[0] + default: + if err := json.Unmarshal(trimmed, &out.Data); err != nil { + return BookingResponse{}, fmt.Errorf("decode booking: %w", err) + } + } + return out, nil +} + +// CancelBooking cancels an existing booking on Cal.com. +// +// Endpoint (confirmed against the Cal.com v2 docs, 2026-06-21): +// +// POST /v2/bookings/{bookingUid}/cancel +// cal-api-version: 2026-02-25 (the same version the create path pins — +// passing the wrong value silently routes to an +// older endpoint shape) +// body: { "cancellationReason": "" } (reason optional) +// +// Authorization is UID-based, mirroring Cal.com's own emailed cancel-link +// model: the booking UID is an unguessable identifier, so possession of it is +// the capability to cancel. The caller is responsible for the abuse guard (the +// public handler rate-limits by IP). uid is URL-path-escaped so a UID with +// path-significant characters can't break out of the route. +// +// A non-2xx response becomes a *CalcomAPIError (via statusErrorWithBody) so the +// handler can classify it; the raw Cal.com message stays server-side only. +func (c *CalcomClient) CancelBooking(ctx context.Context, uid, reason string) error { + body, err := json.Marshal(struct { + CancellationReason string `json:"cancellationReason"` + }{CancellationReason: reason}) + if err != nil { + return err + } + + endpoint := c.baseURL + "/bookings/" + url.PathEscape(uid) + "/cancel" + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body)) + if err != nil { + return err + } + req.Header.Set("Authorization", "Bearer "+c.apiKey) + req.Header.Set("cal-api-version", versionBookings) + req.Header.Set("Content-Type", "application/json") + + resp, err := c.http.Do(req) + if err != nil { + return err + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { + respBody, _ := io.ReadAll(resp.Body) + return statusErrorWithBody("cancel-booking", resp.StatusCode, respBody) + } + return nil +} + +// CalcomAPIError is a typed, non-2xx Cal.com API response. It carries the +// structured pieces the booking handler needs to classify a failure into +// user-actionable, curated copy — the HTTP Status, Cal.com's error Code (e.g. +// "BadRequestException"), and the raw Message ("Attempting to book a meeting in +// the past.") — WITHOUT the handler having to re-parse a flattened log string. +// +// Network/transport failures (DNS, timeout, connection reset) are NOT +// CalcomAPIErrors — those stay plain wrapped errors so the handler routes them +// to the admin-authored Tier-2 message. Only an HTTP response that came back +// with a non-2xx status produces a CalcomAPIError. +// +// Error() reproduces the exact string the previous fmt.Errorf form logged, so +// server-side logging is byte-for-byte unchanged. The Message is for +// classification + logs only — it is NEVER rendered to a visitor verbatim. +type CalcomAPIError struct { + Endpoint string // the Cal.com endpoint that failed, e.g. "booking" + Status int // HTTP status of the non-2xx response + Code string // Cal.com error.code, e.g. "BadRequestException" / "BAD_REQUEST" ("" if absent) + Message string // raw Cal.com error.message ("" if the body carried none) +} + +func (e *CalcomAPIError) Error() string { + // Preserve the historical log shape: "calcom : status [: : ]". + combined := e.Message + if e.Code != "" && e.Message != "" { + combined = e.Code + ": " + e.Message + } else if e.Code != "" { + combined = e.Code + } + if combined == "" { + return fmt.Sprintf("calcom %s: status %d", e.Endpoint, e.Status) + } + return fmt.Sprintf("calcom %s: status %d: %s", e.Endpoint, e.Status, combined) +} + +// statusError builds a sanitized error from a non-2xx response. The status +// code is exposed; the body is captured but only included for non-5xx so we +// don't leak upstream incident traces to operators. +func statusError(endpoint string, resp *http.Response) error { + body, _ := io.ReadAll(resp.Body) + return statusErrorWithBody(endpoint, resp.StatusCode, body) +} + +// statusErrorWithBody returns a *CalcomAPIError for a non-2xx Cal.com response. +// The handler can errors.As it to branch on Status/Code/Message; everything +// else (and log output) sees the same flattened string as before. +func statusErrorWithBody(endpoint string, status int, body []byte) error { + code, msg := extractCalcomError(body) + return &CalcomAPIError{Endpoint: endpoint, Status: status, Code: code, Message: msg} +} + +// extractCalcomError pulls the structured (code, message) pair out of a Cal.com +// error body. Cal.com v2 nests these under `error` as an OBJECT +// ({"error":{"code","message"}}); older/other shapes use a flat top-level +// `message` or a string `error`. All three are handled so a failed booking can +// be classified (and logged) by its real reason ("Attempting to book a meeting +// in the past.") instead of a bare "status 400". Both pieces stay server-side +// only — public handlers always render curated visitor-facing copy. +func extractCalcomError(body []byte) (code, message string) { + var parsed struct { + Message string `json:"message"` + Error json.RawMessage `json:"error"` + } + if err := json.Unmarshal(body, &parsed); err != nil { + return "", "" + } + if parsed.Message != "" { + return "", parsed.Message + } + if len(parsed.Error) == 0 { + return "", "" + } + // `error` may be a structured object {code,message} (v2) or a plain string. + var errObj struct { + Code string `json:"code"` + Message string `json:"message"` + } + if err := json.Unmarshal(parsed.Error, &errObj); err == nil && errObj.Message != "" { + return errObj.Code, errObj.Message + } + var errStr string + if err := json.Unmarshal(parsed.Error, &errStr); err == nil && errStr != "" { + return "", errStr + } + return "", "" +} diff --git a/client_test.go b/client_test.go new file mode 100644 index 0000000..2215eb7 --- /dev/null +++ b/client_test.go @@ -0,0 +1,651 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +// newFakeCalcomServer returns a httptest server whose handler is fn. The +// returned client is preconfigured to talk to it. +func newFakeCalcomServer(t *testing.T, fn http.HandlerFunc) (*httptest.Server, *CalcomClient) { + t.Helper() + srv := httptest.NewServer(fn) + t.Cleanup(srv.Close) + return srv, &CalcomClient{ + http: srv.Client(), + apiKey: "test-key", + baseURL: srv.URL, + } +} + +// TestGetSlots_URLAndHeaders verifies the request shape sent by GetSlots: +// path /v2/slots (or just /slots against our fake), correct query string, and +// the per-endpoint cal-api-version header. +func TestGetSlots_URLAndHeaders(t *testing.T) { + var gotPath string + var gotQuery string + var gotHeaders http.Header + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, r *http.Request) { + gotPath = r.URL.Path + gotQuery = r.URL.RawQuery + gotHeaders = r.Header + _, _ = io.WriteString(w, `{"status":"success","data":{}}`) + }) + _ = srv + + _, err := c.GetSlots(context.Background(), + "alice", "30min", + "2026-05-27T00:00:00Z", "2026-05-28T00:00:00Z", + "America/Toronto") + if err != nil { + t.Fatalf("GetSlots returned error: %v", err) + } + + if gotPath != "/slots" { + t.Errorf("path: got %q, want /slots", gotPath) + } + for _, want := range []string{ + "username=alice", + "eventTypeSlug=30min", + "start=2026-05-27T00%3A00%3A00Z", + "end=2026-05-28T00%3A00%3A00Z", + "timeZone=America%2FToronto", + } { + if !strings.Contains(gotQuery, want) { + t.Errorf("query missing %q (got: %q)", want, gotQuery) + } + } + if got := gotHeaders.Get("cal-api-version"); got != versionSlots { + t.Errorf("cal-api-version: got %q, want %q", got, versionSlots) + } + if got := gotHeaders.Get("Authorization"); got != "Bearer test-key" { + t.Errorf("Authorization: got %q, want Bearer test-key", got) + } +} + +// TestGetSlots_ParsesResponse asserts the Cal.com nested-by-date shape is +// decoded into SlotsResponse.Data correctly. +func TestGetSlots_ParsesResponse(t *testing.T) { + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, _ *http.Request) { + // cal-api-version 2024-09-04 shape: each slot carries `start` (not the + // legacy `time` field). Millisecond precision mirrors the real wire. + _, _ = io.WriteString(w, `{ + "status":"success", + "data":{ + "2026-05-27":[{"start":"2026-05-27T13:00:00.000Z"},{"start":"2026-05-27T13:30:00.000Z"}], + "2026-05-28":[{"start":"2026-05-28T10:00:00.000Z"}] + } + }`) + }) + _ = srv + + got, err := c.GetSlots(context.Background(), "alice", "30min", "2026-05-27T00:00:00Z", "2026-05-29T00:00:00Z", "UTC") + if err != nil { + t.Fatalf("GetSlots: %v", err) + } + if len(got.Data["2026-05-27"]) != 2 { + t.Errorf("expected 2 slots for 2026-05-27, got %d", len(got.Data["2026-05-27"])) + } + if got.Data["2026-05-27"][0].When() != "2026-05-27T13:00:00.000Z" { + t.Errorf("first slot start mismatch: %q", got.Data["2026-05-27"][0].When()) + } + if len(got.Data["2026-05-28"]) != 1 { + t.Errorf("expected 1 slot for 2026-05-28, got %d", len(got.Data["2026-05-28"])) + } +} + +// TestGetSlots_LegacyTimeFieldFallback asserts that if Cal.com (or a rolled-back +// cal-api-version) replies with the older {"time":...} slot shape, SlotEntry.When() +// still resolves the timestamp. Guards the dual-shape decode that protects the +// widget from Cal.com's documented response-shape drift across API versions. +func TestGetSlots_LegacyTimeFieldFallback(t *testing.T) { + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, _ *http.Request) { + _, _ = io.WriteString(w, `{"status":"success","data":{"2026-05-27":[{"time":"2026-05-27T13:00:00Z"}]}}`) + }) + _ = srv + + got, err := c.GetSlots(context.Background(), "alice", "30min", "2026-05-27T00:00:00Z", "2026-05-28T00:00:00Z", "UTC") + if err != nil { + t.Fatalf("GetSlots: %v", err) + } + if len(got.Data["2026-05-27"]) != 1 { + t.Fatalf("expected 1 slot, got %d", len(got.Data["2026-05-27"])) + } + if got.Data["2026-05-27"][0].When() != "2026-05-27T13:00:00Z" { + t.Errorf("legacy time field not resolved by When(): %q", got.Data["2026-05-27"][0].When()) + } +} + +// TestListEventTypes_HeaderAndParsing asserts the event-types header and the +// EventTypeSummary parsing including lengthInMinutes. +func TestListEventTypes_HeaderAndParsing(t *testing.T) { + var gotHeaders http.Header + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, r *http.Request) { + gotHeaders = r.Header + _, _ = io.WriteString(w, `{ + "status":"success", + "data":[ + {"id":1,"slug":"30min","title":"30 Minute Meeting","lengthInMinutes":30}, + {"id":2,"slug":"60min","title":"60 Minute Meeting","lengthInMinutes":60} + ] + }`) + }) + _ = srv + + ets, err := c.ListEventTypes(context.Background(), "alice") + if err != nil { + t.Fatalf("ListEventTypes: %v", err) + } + if got := gotHeaders.Get("cal-api-version"); got != versionEventTypes { + t.Errorf("cal-api-version: got %q, want %q", got, versionEventTypes) + } + if len(ets) != 2 { + t.Fatalf("expected 2 event types, got %d", len(ets)) + } + if ets[0].LengthInMinutes != 30 { + t.Errorf("lengthInMinutes not parsed: %+v", ets[0]) + } + if ets[1].Slug != "60min" { + t.Errorf("second slug wrong: %+v", ets[1]) + } +} + +// TestGetEventType_BookingFieldsParsing verifies the full EventType shape +// including custom bookingFields with options is parsed correctly. +func TestGetEventType_BookingFieldsParsing(t *testing.T) { + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, _ *http.Request) { + _, _ = io.WriteString(w, `{ + "status":"success", + "data":[{ + "id":42, + "slug":"intake", + "title":"Intake Call", + "lengthInMinutes":15, + "bookingFields":[ + {"slug":"name","label":"Your name","type":"name","required":true}, + {"slug":"company_size","label":"Company size","type":"select","required":true, + "options":[{"label":"1-10","value":"small"},{"label":"11-50","value":"medium"}]} + ] + }] + }`) + }) + _ = srv + + et, err := c.GetEventType(context.Background(), "alice", "intake") + if err != nil { + t.Fatalf("GetEventType: %v", err) + } + if et.ID != 42 { + t.Errorf("ID: got %d, want 42", et.ID) + } + if len(et.BookingFields) != 2 { + t.Fatalf("expected 2 booking fields, got %d", len(et.BookingFields)) + } + if et.BookingFields[1].Type != "select" || len(et.BookingFields[1].Options) != 2 { + t.Errorf("select field options not parsed: %+v", et.BookingFields[1]) + } + if et.BookingFields[1].Options[0].Value != "small" { + t.Errorf("first option value wrong: %+v", et.BookingFields[1].Options[0]) + } +} + +// TestGetEventType_RealV2ShapeNormalizesFields pins the ACTUAL Cal.com v2 +// /event-types bookingFields shape (cal-api-version 2024-06-14), which differs +// from what the plugin originally assumed: +// +// - the field identifier is "slug", NOT "name" +// - default/system fields return an empty "label" (Cal.com's own booker +// derives the visible label client-side from i18n keyed on the field type) +// - some system fields are "hidden":true and must not be rendered +// +// Decoding this with the old struct produced inputs with name="" and blank +// labels — invisible AND unsubmittable. GetEventType must normalize: +// slug→Name, a resolved label for system fields, and drop hidden fields. +func TestGetEventType_RealV2ShapeNormalizesFields(t *testing.T) { + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, _ *http.Request) { + _, _ = io.WriteString(w, `{ + "status":"success", + "data":[{ + "id":7, + "slug":"30min", + "title":"30 Min Meeting", + "lengthInMinutes":30, + "bookingFields":[ + {"type":"name","slug":"name","required":true,"label":"","isDefault":true}, + {"type":"email","slug":"email","required":true,"label":"","isDefault":true}, + {"type":"phone","slug":"attendeePhoneNumber","required":true,"label":"","placeholder":"","isDefault":true}, + {"type":"textarea","slug":"notes","required":false,"label":"","isDefault":true}, + {"type":"text","slug":"company","required":false,"label":"Company name","isDefault":false}, + {"type":"text","slug":"title","required":false,"label":"","hidden":true,"isDefault":true} + ] + }] + }`) + }) + _ = srv + + et, err := c.GetEventType(context.Background(), "bidbuddy", "30min") + if err != nil { + t.Fatalf("GetEventType: %v", err) + } + + // The hidden "title" system field must be dropped. + if len(et.BookingFields) != 5 { + t.Fatalf("expected 5 visible booking fields (hidden dropped), got %d: %+v", len(et.BookingFields), et.BookingFields) + } + + want := []struct { + name string + label string + }{ + {"name", "Name"}, + {"email", "Email"}, + {"attendeePhoneNumber", "Phone number"}, + {"notes", "Additional notes"}, + {"company", "Company name"}, // custom field: explicit label preserved + } + for i, w := range want { + got := et.BookingFields[i] + if got.Name != w.name { + t.Errorf("field %d Name: got %q, want %q (slug must populate Name)", i, got.Name, w.name) + } + if got.Label != w.label { + t.Errorf("field %d Label: got %q, want %q", i, got.Label, w.label) + } + } +} + +// TestGetEventType_NotFound asserts a descriptive error is returned when the +// event type isn't found in the response. +func TestGetEventType_NotFound(t *testing.T) { + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, _ *http.Request) { + _, _ = io.WriteString(w, `{"status":"success","data":[]}`) + }) + _ = srv + + _, err := c.GetEventType(context.Background(), "alice", "nope") + if err == nil { + t.Fatalf("expected error for missing event type") + } + if !strings.Contains(err.Error(), "not found") { + t.Errorf("expected 'not found' in error, got: %v", err) + } +} + +// TestCreateBooking_RequestShape verifies the WO-008 body contract: +// uses eventTypeSlug + username (no eventTypeId); attendee has timeZone; +// bookingFieldsResponses carries custom fields; cal-api-version header is the +// bookings-specific 2026-02-25. +func TestCreateBooking_RequestShape(t *testing.T) { + var gotBody []byte + var gotHeaders http.Header + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, r *http.Request) { + gotBody, _ = io.ReadAll(r.Body) + gotHeaders = r.Header + _, _ = io.WriteString(w, `{"status":"success","data":{"uid":"abc","rescheduleUrl":"https://cal.com/reschedule/abc","cancelUrl":"https://cal.com/booking/abc?cancel=true"}}`) + }) + _ = srv + + _, err := c.CreateBooking(context.Background(), BookingRequest{ + EventTypeSlug: "30min", + Username: "alice", + Start: "2026-05-27T10:00:00Z", + Attendee: BookingAttendee{ + Name: "Bob", Email: "bob@example.com", TimeZone: "America/Toronto", + }, + BookingFieldsResponses: map[string]any{"q1": "yes"}, + }) + if err != nil { + t.Fatalf("CreateBooking: %v", err) + } + if got := gotHeaders.Get("cal-api-version"); got != versionBookings { + t.Errorf("cal-api-version: got %q, want %q", got, versionBookings) + } + + var parsed map[string]any + if err := json.Unmarshal(gotBody, &parsed); err != nil { + t.Fatalf("decode body: %v", err) + } + if parsed["eventTypeSlug"] != "30min" { + t.Errorf("eventTypeSlug missing/wrong: %v", parsed["eventTypeSlug"]) + } + if parsed["username"] != "alice" { + t.Errorf("username missing/wrong: %v", parsed["username"]) + } + if _, present := parsed["eventTypeId"]; present { + t.Errorf("body must NOT contain eventTypeId (old shape); body=%s", string(gotBody)) + } + att, _ := parsed["attendee"].(map[string]any) + if att["timeZone"] != "America/Toronto" { + t.Errorf("attendee.timeZone wrong: %v", att["timeZone"]) + } + resps, _ := parsed["bookingFieldsResponses"].(map[string]any) + if resps["q1"] != "yes" { + t.Errorf("bookingFieldsResponses.q1 wrong: %v", resps) + } +} + +// TestCreateBooking_ParsesLocationAndUID verifies the 2026-02-25 response: the +// booking UID and the canonical `location` meeting URL parse, and MeetingLink() +// resolves to `location`. The response carries no reschedule/cancel URLs, so +// those fields were intentionally removed from BookingResponseData. +func TestCreateBooking_ParsesLocationAndUID(t *testing.T) { + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, _ *http.Request) { + _, _ = io.WriteString(w, `{"status":"success","data":{ + "id":123, + "uid":"xyz", + "start":"2026-05-27T10:00:00Z", + "end":"2026-05-27T10:30:00Z", + "location":"https://meet.example.com/xyz" + }}`) + }) + _ = srv + + resp, err := c.CreateBooking(context.Background(), BookingRequest{ + EventTypeSlug: "30min", Username: "alice", Start: "2026-05-27T10:00:00Z", + Attendee: BookingAttendee{Name: "Bob", Email: "b@x.com", TimeZone: "UTC"}, + }) + if err != nil { + t.Fatalf("CreateBooking: %v", err) + } + if resp.Data.UID != "xyz" { + t.Errorf("UID: got %q, want xyz", resp.Data.UID) + } + if resp.Data.MeetingLink() != "https://meet.example.com/xyz" { + t.Errorf("MeetingLink: got %q, want the `location` URL", resp.Data.MeetingLink()) + } +} + +// TestBookingMeetingLink_Resolution covers MeetingLink()'s field precedence and +// the URL guard that prevents a physical-address/phone `location` (used by +// non-video event types) from being surfaced as a clickable link. +func TestBookingMeetingLink_Resolution(t *testing.T) { + cases := []struct { + name string + data BookingResponseData + want string + }{ + {"location url preferred", BookingResponseData{Location: "https://a/x", MeetingURL: "https://b/y"}, "https://a/x"}, + {"falls back to meetingUrl", BookingResponseData{Location: "", MeetingURL: "https://b/y"}, "https://b/y"}, + {"non-url location is not a link", BookingResponseData{Location: "123 Main St, Perth", MeetingURL: ""}, ""}, + {"non-url location falls back to meetingUrl", BookingResponseData{Location: "+61 8 1234 5678", MeetingURL: "https://b/y"}, "https://b/y"}, + {"nothing", BookingResponseData{}, ""}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := tc.data.MeetingLink(); got != tc.want { + t.Errorf("MeetingLink() = %q, want %q", got, tc.want) + } + }) + } +} + +// TestCreateBooking_RecurringArrayData asserts that a recurring event type — +// whose response `data` is an ARRAY of occurrences — no longer hard-fails the +// booking. The first occurrence is surfaced (its UID + location). +func TestCreateBooking_RecurringArrayData(t *testing.T) { + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, _ *http.Request) { + _, _ = io.WriteString(w, `{"status":"success","data":[ + {"uid":"occ-1","start":"2026-05-27T10:00:00Z","location":"https://meet.example.com/occ-1"}, + {"uid":"occ-2","start":"2026-06-03T10:00:00Z","location":"https://meet.example.com/occ-2"} + ]}`) + }) + _ = srv + + resp, err := c.CreateBooking(context.Background(), BookingRequest{ + EventTypeSlug: "weekly", Username: "alice", Start: "2026-05-27T10:00:00Z", + Attendee: BookingAttendee{Name: "Bob", Email: "b@x.com", TimeZone: "UTC"}, + }) + if err != nil { + t.Fatalf("CreateBooking (recurring): %v", err) + } + if resp.Data.UID != "occ-1" { + t.Errorf("recurring booking should surface first occurrence UID; got %q", resp.Data.UID) + } + if resp.Data.MeetingLink() != "https://meet.example.com/occ-1" { + t.Errorf("recurring MeetingLink: got %q", resp.Data.MeetingLink()) + } +} + +// TestCancelBooking_RequestShape verifies CancelBooking sends the confirmed v2 +// shape: POST /bookings/{uid}/cancel with the bookings cal-api-version, the +// Bearer token, and a {"cancellationReason": ...} body. The uid must be +// URL-path-escaped so a path-significant character can't break out of the route. +func TestCancelBooking_RequestShape(t *testing.T) { + var gotMethod, gotEscapedPath string + var gotHeaders http.Header + var gotBody []byte + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, r *http.Request) { + gotMethod = r.Method + // EscapedPath() preserves the on-the-wire escaping (r.URL.Path is decoded + // by the server, hiding whether the uid was escaped). This is what proves + // the uid was path-escaped and couldn't break out of the route. + gotEscapedPath = r.URL.EscapedPath() + gotHeaders = r.Header + gotBody, _ = io.ReadAll(r.Body) + _, _ = io.WriteString(w, `{"status":"success","data":{"id":1,"uid":"abc","status":"cancelled"}}`) + }) + _ = srv + + // A uid with a path-significant character (slash) must be escaped into a + // single path segment, not split the route. + if err := c.CancelBooking(context.Background(), "uid/with space", "Cancelled by attendee"); err != nil { + t.Fatalf("CancelBooking: %v", err) + } + if gotMethod != http.MethodPost { + t.Errorf("method: got %q, want POST", gotMethod) + } + if gotEscapedPath != "/bookings/uid%2Fwith%20space/cancel" { + t.Errorf("escaped path: got %q, want /bookings/uid%%2Fwith%%20space/cancel (uid path-escaped)", gotEscapedPath) + } + if got := gotHeaders.Get("cal-api-version"); got != versionBookings { + t.Errorf("cal-api-version: got %q, want %q", got, versionBookings) + } + if got := gotHeaders.Get("Authorization"); got != "Bearer test-key" { + t.Errorf("Authorization: got %q, want Bearer test-key", got) + } + var parsed map[string]any + if err := json.Unmarshal(gotBody, &parsed); err != nil { + t.Fatalf("decode body: %v", err) + } + if parsed["cancellationReason"] != "Cancelled by attendee" { + t.Errorf("cancellationReason: got %v, want %q", parsed["cancellationReason"], "Cancelled by attendee") + } +} + +// TestCancelBooking_NonOKReturnsAPIError confirms a non-2xx cancel response +// surfaces as a *CalcomAPIError carrying the status (so the handler can classify +// it / log it) and is never silently swallowed. +func TestCancelBooking_NonOKReturnsAPIError(t *testing.T) { + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = io.WriteString(w, `{"error":{"code":"NotFoundException","message":"Booking not found"}}`) + }) + _ = srv + + err := c.CancelBooking(context.Background(), "missing-uid", "reason") + if err == nil { + t.Fatalf("expected error from 404 cancel response") + } + var apiErr *CalcomAPIError + if !errors.As(err, &apiErr) { + t.Fatalf("expected *CalcomAPIError, got %T: %v", err, err) + } + if apiErr.Status != http.StatusNotFound { + t.Errorf("status: got %d, want 404", apiErr.Status) + } + if !strings.Contains(err.Error(), "404") { + t.Errorf("error should mention status code, got: %v", err) + } +} + +// TestCancelBooking_OmitsReasonWhenEmpty confirms an empty reason still sends a +// well-formed body (cancellationReason is optional per the v2 docs; we always +// send the field, empty when no reason is supplied). +func TestCancelBooking_AcceptsEmptyReason(t *testing.T) { + var gotBody []byte + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, r *http.Request) { + gotBody, _ = io.ReadAll(r.Body) + _, _ = io.WriteString(w, `{"status":"success","data":{"status":"cancelled"}}`) + }) + _ = srv + + if err := c.CancelBooking(context.Background(), "abc", ""); err != nil { + t.Fatalf("CancelBooking with empty reason: %v", err) + } + if !strings.Contains(string(gotBody), `"cancellationReason":""`) { + t.Errorf("body should carry an (empty) cancellationReason field; got %s", string(gotBody)) + } +} + +// TestClient_ErrorResponseSanitization confirms 4xx responses surface as +// errors (not panics) and that the error string includes the status code so +// callers can branch on it. +func TestClient_ErrorResponseSanitization(t *testing.T) { + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + _, _ = io.WriteString(w, `{"error":"unauthorized","message":"Invalid API key"}`) + }) + _ = srv + + _, err := c.GetSlots(context.Background(), "alice", "30min", "2026-05-27T00:00:00Z", "2026-05-28T00:00:00Z", "UTC") + if err == nil { + t.Fatalf("expected error from 401 response") + } + if !strings.Contains(err.Error(), "401") { + t.Errorf("error should mention status code, got: %v", err) + } +} + +// TestClient_HandlesNetworkFailure ensures a connection-level error surfaces +// without panicking. +func TestClient_HandlesNetworkFailure(t *testing.T) { + c := &CalcomClient{http: &http.Client{}, apiKey: "k", baseURL: "http://127.0.0.1:1"} + _, err := c.GetSlots(context.Background(), "alice", "30min", "x", "y", "UTC") + if err == nil { + t.Fatalf("expected error from unreachable server") + } +} + +// Compile-time guard: ensure SlotEntry retains BOTH timestamp fields. Catches +// accidental renames during refactors — the dual shape (2024-09-04 `start` plus +// legacy `time`) is what keeps the widget resilient to Cal.com version drift. +var _ = SlotEntry{Start: "", Time: ""} + +// Compile-time guard for the legacy fmt import in test diagnostics. +var _ = "" + +// TestGetEventType_SendsEventSlugQuery verifies that GetEventType sends BOTH +// `username=` and `eventSlug=` as query params. The existing booking-fields +// parsing test (TestGetEventType_BookingFieldsParsing) does not capture the +// outbound URL — without this assertion the client could silently regress to +// omitting one or both params and the broken request would still parse the +// stub response in tests. +func TestGetEventType_SendsEventSlugQuery(t *testing.T) { + var gotQuery string + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, r *http.Request) { + gotQuery = r.URL.RawQuery + _, _ = io.WriteString(w, `{"status":"success","data":[{"id":1,"slug":"intake","title":"x","lengthInMinutes":15,"bookingFields":[]}]}`) + }) + _ = srv + + if _, err := c.GetEventType(context.Background(), "alice", "intake"); err != nil { + t.Fatalf("GetEventType: %v", err) + } + if !strings.Contains(gotQuery, "eventSlug=intake") { + t.Errorf("outbound query missing eventSlug=intake (got: %q)", gotQuery) + } + if !strings.Contains(gotQuery, "username=alice") { + t.Errorf("outbound query missing username=alice (got: %q)", gotQuery) + } +} + +// TestListEventTypes_SendsUsernameQuery verifies that ListEventTypes propagates +// the username argument to Cal.com as a query param. Without this, the editor +// dropdown would silently fetch the API-key owner's own event types instead of +// the user the admin selected. +func TestListEventTypes_SendsUsernameQuery(t *testing.T) { + var gotQuery string + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, r *http.Request) { + gotQuery = r.URL.RawQuery + _, _ = io.WriteString(w, `{"status":"success","data":[]}`) + }) + _ = srv + + if _, err := c.ListEventTypes(context.Background(), "alice"); err != nil { + t.Fatalf("ListEventTypes: %v", err) + } + if !strings.Contains(gotQuery, "username=alice") { + t.Errorf("outbound query missing username=alice (got: %q)", gotQuery) + } +} + +// TestGetMe_URLAndHeaders verifies the request shape sent by GetMe: +// path /me, no query string, and the cal-api-version header for the /me endpoint. +func TestGetMe_URLAndHeaders(t *testing.T) { + var gotPath string + var gotHeaders http.Header + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, r *http.Request) { + gotPath = r.URL.Path + gotHeaders = r.Header + _, _ = io.WriteString(w, `{"status":"success","data":{"username":"alice","email":"alice@example.com"}}`) + }) + _ = srv + + _, err := c.GetMe(context.Background()) + if err != nil { + t.Fatalf("GetMe returned error: %v", err) + } + if gotPath != "/me" { + t.Errorf("path: got %q, want /me", gotPath) + } + if got := gotHeaders.Get("cal-api-version"); got != versionMe { + t.Errorf("cal-api-version: got %q, want %q", got, versionMe) + } + if got := gotHeaders.Get("Authorization"); got != "Bearer test-key" { + t.Errorf("Authorization: got %q, want Bearer test-key", got) + } +} + +// TestGetMe_ParsesResponse confirms the {status, data:{username, email}} envelope +// is decoded into MeResponse correctly. +func TestGetMe_ParsesResponse(t *testing.T) { + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, _ *http.Request) { + _, _ = io.WriteString(w, `{"status":"success","data":{"username":"alice","email":"alice@example.com"}}`) + }) + _ = srv + + me, err := c.GetMe(context.Background()) + if err != nil { + t.Fatalf("GetMe: %v", err) + } + if me.Username != "alice" { + t.Errorf("Username: got %q, want alice", me.Username) + } + if me.Email != "alice@example.com" { + t.Errorf("Email: got %q, want alice@example.com", me.Email) + } +} + +// TestGetMe_BubblesUpstreamError ensures a non-2xx Cal.com response surfaces +// as an error from GetMe rather than a zero-value MeResponse. +func TestGetMe_BubblesUpstreamError(t *testing.T) { + srv, c := newFakeCalcomServer(t, func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + _, _ = io.WriteString(w, `{"message":"invalid api key"}`) + }) + _ = srv + + _, err := c.GetMe(context.Background()) + if err == nil { + t.Fatal("expected error on 401, got nil") + } + if !strings.Contains(err.Error(), "401") { + t.Errorf("error should mention 401, got: %v", err) + } +} diff --git a/countries.go b/countries.go new file mode 100644 index 0000000..674b5b6 --- /dev/null +++ b/countries.go @@ -0,0 +1,257 @@ +package main + +// Country is one ITU dialing entry for the booking phone country selector. +type Country struct { + Name string + Code string // ISO 3166-1 alpha-2 + Dial string // E.164 calling code, e.g. "+61" +} + +// countries is the full country list (sorted by name) rendered in the phone +// country combobox, and the source of truth for country lookups (countryByCode, +// countryLabel). Generated from the public CountryCodes dataset +// (gist anubhavshrimal/75f6183458db8c453306f93521e93d37). +var countries = []Country{ + {Name: "Afghanistan", Code: "AF", Dial: "+93"}, + {Name: "Aland Islands", Code: "AX", Dial: "+358"}, + {Name: "Albania", Code: "AL", Dial: "+355"}, + {Name: "Algeria", Code: "DZ", Dial: "+213"}, + {Name: "AmericanSamoa", Code: "AS", Dial: "+1684"}, + {Name: "Andorra", Code: "AD", Dial: "+376"}, + {Name: "Angola", Code: "AO", Dial: "+244"}, + {Name: "Anguilla", Code: "AI", Dial: "+1264"}, + {Name: "Antarctica", Code: "AQ", Dial: "+672"}, + {Name: "Antigua and Barbuda", Code: "AG", Dial: "+1268"}, + {Name: "Argentina", Code: "AR", Dial: "+54"}, + {Name: "Armenia", Code: "AM", Dial: "+374"}, + {Name: "Aruba", Code: "AW", Dial: "+297"}, + {Name: "Australia", Code: "AU", Dial: "+61"}, + {Name: "Austria", Code: "AT", Dial: "+43"}, + {Name: "Azerbaijan", Code: "AZ", Dial: "+994"}, + {Name: "Bahamas", Code: "BS", Dial: "+1242"}, + {Name: "Bahrain", Code: "BH", Dial: "+973"}, + {Name: "Bangladesh", Code: "BD", Dial: "+880"}, + {Name: "Barbados", Code: "BB", Dial: "+1246"}, + {Name: "Belarus", Code: "BY", Dial: "+375"}, + {Name: "Belgium", Code: "BE", Dial: "+32"}, + {Name: "Belize", Code: "BZ", Dial: "+501"}, + {Name: "Benin", Code: "BJ", Dial: "+229"}, + {Name: "Bermuda", Code: "BM", Dial: "+1441"}, + {Name: "Bhutan", Code: "BT", Dial: "+975"}, + {Name: "Bolivia, Plurinational State of", Code: "BO", Dial: "+591"}, + {Name: "Bosnia and Herzegovina", Code: "BA", Dial: "+387"}, + {Name: "Botswana", Code: "BW", Dial: "+267"}, + {Name: "Brazil", Code: "BR", Dial: "+55"}, + {Name: "British Indian Ocean Territory", Code: "IO", Dial: "+246"}, + {Name: "Brunei Darussalam", Code: "BN", Dial: "+673"}, + {Name: "Bulgaria", Code: "BG", Dial: "+359"}, + {Name: "Burkina Faso", Code: "BF", Dial: "+226"}, + {Name: "Burundi", Code: "BI", Dial: "+257"}, + {Name: "Cambodia", Code: "KH", Dial: "+855"}, + {Name: "Cameroon", Code: "CM", Dial: "+237"}, + {Name: "Canada", Code: "CA", Dial: "+1"}, + {Name: "Cape Verde", Code: "CV", Dial: "+238"}, + {Name: "Cayman Islands", Code: "KY", Dial: "+345"}, + {Name: "Central African Republic", Code: "CF", Dial: "+236"}, + {Name: "Chad", Code: "TD", Dial: "+235"}, + {Name: "Chile", Code: "CL", Dial: "+56"}, + {Name: "China", Code: "CN", Dial: "+86"}, + {Name: "Christmas Island", Code: "CX", Dial: "+61"}, + {Name: "Cocos (Keeling) Islands", Code: "CC", Dial: "+61"}, + {Name: "Colombia", Code: "CO", Dial: "+57"}, + {Name: "Comoros", Code: "KM", Dial: "+269"}, + {Name: "Congo", Code: "CG", Dial: "+242"}, + {Name: "Congo, The Democratic Republic of the Congo", Code: "CD", Dial: "+243"}, + {Name: "Cook Islands", Code: "CK", Dial: "+682"}, + {Name: "Costa Rica", Code: "CR", Dial: "+506"}, + {Name: "Cote d'Ivoire", Code: "CI", Dial: "+225"}, + {Name: "Croatia", Code: "HR", Dial: "+385"}, + {Name: "Cuba", Code: "CU", Dial: "+53"}, + {Name: "Cyprus", Code: "CY", Dial: "+357"}, + {Name: "Czech Republic", Code: "CZ", Dial: "+420"}, + {Name: "Denmark", Code: "DK", Dial: "+45"}, + {Name: "Djibouti", Code: "DJ", Dial: "+253"}, + {Name: "Dominica", Code: "DM", Dial: "+1767"}, + {Name: "Dominican Republic", Code: "DO", Dial: "+1849"}, + {Name: "Ecuador", Code: "EC", Dial: "+593"}, + {Name: "Egypt", Code: "EG", Dial: "+20"}, + {Name: "El Salvador", Code: "SV", Dial: "+503"}, + {Name: "Equatorial Guinea", Code: "GQ", Dial: "+240"}, + {Name: "Eritrea", Code: "ER", Dial: "+291"}, + {Name: "Estonia", Code: "EE", Dial: "+372"}, + {Name: "Ethiopia", Code: "ET", Dial: "+251"}, + {Name: "Falkland Islands (Malvinas)", Code: "FK", Dial: "+500"}, + {Name: "Faroe Islands", Code: "FO", Dial: "+298"}, + {Name: "Fiji", Code: "FJ", Dial: "+679"}, + {Name: "Finland", Code: "FI", Dial: "+358"}, + {Name: "France", Code: "FR", Dial: "+33"}, + {Name: "French Guiana", Code: "GF", Dial: "+594"}, + {Name: "French Polynesia", Code: "PF", Dial: "+689"}, + {Name: "Gabon", Code: "GA", Dial: "+241"}, + {Name: "Gambia", Code: "GM", Dial: "+220"}, + {Name: "Georgia", Code: "GE", Dial: "+995"}, + {Name: "Germany", Code: "DE", Dial: "+49"}, + {Name: "Ghana", Code: "GH", Dial: "+233"}, + {Name: "Gibraltar", Code: "GI", Dial: "+350"}, + {Name: "Greece", Code: "GR", Dial: "+30"}, + {Name: "Greenland", Code: "GL", Dial: "+299"}, + {Name: "Grenada", Code: "GD", Dial: "+1473"}, + {Name: "Guadeloupe", Code: "GP", Dial: "+590"}, + {Name: "Guam", Code: "GU", Dial: "+1671"}, + {Name: "Guatemala", Code: "GT", Dial: "+502"}, + {Name: "Guernsey", Code: "GG", Dial: "+44"}, + {Name: "Guinea", Code: "GN", Dial: "+224"}, + {Name: "Guinea-Bissau", Code: "GW", Dial: "+245"}, + {Name: "Guyana", Code: "GY", Dial: "+595"}, + {Name: "Haiti", Code: "HT", Dial: "+509"}, + {Name: "Holy See (Vatican City State)", Code: "VA", Dial: "+379"}, + {Name: "Honduras", Code: "HN", Dial: "+504"}, + {Name: "Hong Kong", Code: "HK", Dial: "+852"}, + {Name: "Hungary", Code: "HU", Dial: "+36"}, + {Name: "Iceland", Code: "IS", Dial: "+354"}, + {Name: "India", Code: "IN", Dial: "+91"}, + {Name: "Indonesia", Code: "ID", Dial: "+62"}, + {Name: "Iran, Islamic Republic of Persian Gulf", Code: "IR", Dial: "+98"}, + {Name: "Iraq", Code: "IQ", Dial: "+964"}, + {Name: "Ireland", Code: "IE", Dial: "+353"}, + {Name: "Isle of Man", Code: "IM", Dial: "+44"}, + {Name: "Israel", Code: "IL", Dial: "+972"}, + {Name: "Italy", Code: "IT", Dial: "+39"}, + {Name: "Jamaica", Code: "JM", Dial: "+1876"}, + {Name: "Japan", Code: "JP", Dial: "+81"}, + {Name: "Jersey", Code: "JE", Dial: "+44"}, + {Name: "Jordan", Code: "JO", Dial: "+962"}, + {Name: "Kazakhstan", Code: "KZ", Dial: "+77"}, + {Name: "Kenya", Code: "KE", Dial: "+254"}, + {Name: "Kiribati", Code: "KI", Dial: "+686"}, + {Name: "Korea, Democratic People's Republic of Korea", Code: "KP", Dial: "+850"}, + {Name: "Korea, Republic of South Korea", Code: "KR", Dial: "+82"}, + {Name: "Kuwait", Code: "KW", Dial: "+965"}, + {Name: "Kyrgyzstan", Code: "KG", Dial: "+996"}, + {Name: "Laos", Code: "LA", Dial: "+856"}, + {Name: "Latvia", Code: "LV", Dial: "+371"}, + {Name: "Lebanon", Code: "LB", Dial: "+961"}, + {Name: "Lesotho", Code: "LS", Dial: "+266"}, + {Name: "Liberia", Code: "LR", Dial: "+231"}, + {Name: "Libyan Arab Jamahiriya", Code: "LY", Dial: "+218"}, + {Name: "Liechtenstein", Code: "LI", Dial: "+423"}, + {Name: "Lithuania", Code: "LT", Dial: "+370"}, + {Name: "Luxembourg", Code: "LU", Dial: "+352"}, + {Name: "Macao", Code: "MO", Dial: "+853"}, + {Name: "Macedonia", Code: "MK", Dial: "+389"}, + {Name: "Madagascar", Code: "MG", Dial: "+261"}, + {Name: "Malawi", Code: "MW", Dial: "+265"}, + {Name: "Malaysia", Code: "MY", Dial: "+60"}, + {Name: "Maldives", Code: "MV", Dial: "+960"}, + {Name: "Mali", Code: "ML", Dial: "+223"}, + {Name: "Malta", Code: "MT", Dial: "+356"}, + {Name: "Marshall Islands", Code: "MH", Dial: "+692"}, + {Name: "Martinique", Code: "MQ", Dial: "+596"}, + {Name: "Mauritania", Code: "MR", Dial: "+222"}, + {Name: "Mauritius", Code: "MU", Dial: "+230"}, + {Name: "Mayotte", Code: "YT", Dial: "+262"}, + {Name: "Mexico", Code: "MX", Dial: "+52"}, + {Name: "Micronesia, Federated States of Micronesia", Code: "FM", Dial: "+691"}, + {Name: "Moldova", Code: "MD", Dial: "+373"}, + {Name: "Monaco", Code: "MC", Dial: "+377"}, + {Name: "Mongolia", Code: "MN", Dial: "+976"}, + {Name: "Montenegro", Code: "ME", Dial: "+382"}, + {Name: "Montserrat", Code: "MS", Dial: "+1664"}, + {Name: "Morocco", Code: "MA", Dial: "+212"}, + {Name: "Mozambique", Code: "MZ", Dial: "+258"}, + {Name: "Myanmar", Code: "MM", Dial: "+95"}, + {Name: "Namibia", Code: "NA", Dial: "+264"}, + {Name: "Nauru", Code: "NR", Dial: "+674"}, + {Name: "Nepal", Code: "NP", Dial: "+977"}, + {Name: "Netherlands", Code: "NL", Dial: "+31"}, + {Name: "Netherlands Antilles", Code: "AN", Dial: "+599"}, + {Name: "New Caledonia", Code: "NC", Dial: "+687"}, + {Name: "New Zealand", Code: "NZ", Dial: "+64"}, + {Name: "Nicaragua", Code: "NI", Dial: "+505"}, + {Name: "Niger", Code: "NE", Dial: "+227"}, + {Name: "Nigeria", Code: "NG", Dial: "+234"}, + {Name: "Niue", Code: "NU", Dial: "+683"}, + {Name: "Norfolk Island", Code: "NF", Dial: "+672"}, + {Name: "Northern Mariana Islands", Code: "MP", Dial: "+1670"}, + {Name: "Norway", Code: "NO", Dial: "+47"}, + {Name: "Oman", Code: "OM", Dial: "+968"}, + {Name: "Pakistan", Code: "PK", Dial: "+92"}, + {Name: "Palau", Code: "PW", Dial: "+680"}, + {Name: "Palestinian Territory, Occupied", Code: "PS", Dial: "+970"}, + {Name: "Panama", Code: "PA", Dial: "+507"}, + {Name: "Papua New Guinea", Code: "PG", Dial: "+675"}, + {Name: "Paraguay", Code: "PY", Dial: "+595"}, + {Name: "Peru", Code: "PE", Dial: "+51"}, + {Name: "Philippines", Code: "PH", Dial: "+63"}, + {Name: "Pitcairn", Code: "PN", Dial: "+872"}, + {Name: "Poland", Code: "PL", Dial: "+48"}, + {Name: "Portugal", Code: "PT", Dial: "+351"}, + {Name: "Puerto Rico", Code: "PR", Dial: "+1939"}, + {Name: "Qatar", Code: "QA", Dial: "+974"}, + {Name: "Reunion", Code: "RE", Dial: "+262"}, + {Name: "Romania", Code: "RO", Dial: "+40"}, + {Name: "Russia", Code: "RU", Dial: "+7"}, + {Name: "Rwanda", Code: "RW", Dial: "+250"}, + {Name: "Saint Barthelemy", Code: "BL", Dial: "+590"}, + {Name: "Saint Helena, Ascension and Tristan Da Cunha", Code: "SH", Dial: "+290"}, + {Name: "Saint Kitts and Nevis", Code: "KN", Dial: "+1869"}, + {Name: "Saint Lucia", Code: "LC", Dial: "+1758"}, + {Name: "Saint Martin", Code: "MF", Dial: "+590"}, + {Name: "Saint Pierre and Miquelon", Code: "PM", Dial: "+508"}, + {Name: "Saint Vincent and the Grenadines", Code: "VC", Dial: "+1784"}, + {Name: "Samoa", Code: "WS", Dial: "+685"}, + {Name: "San Marino", Code: "SM", Dial: "+378"}, + {Name: "Sao Tome and Principe", Code: "ST", Dial: "+239"}, + {Name: "Saudi Arabia", Code: "SA", Dial: "+966"}, + {Name: "Senegal", Code: "SN", Dial: "+221"}, + {Name: "Serbia", Code: "RS", Dial: "+381"}, + {Name: "Seychelles", Code: "SC", Dial: "+248"}, + {Name: "Sierra Leone", Code: "SL", Dial: "+232"}, + {Name: "Singapore", Code: "SG", Dial: "+65"}, + {Name: "Slovakia", Code: "SK", Dial: "+421"}, + {Name: "Slovenia", Code: "SI", Dial: "+386"}, + {Name: "Solomon Islands", Code: "SB", Dial: "+677"}, + {Name: "Somalia", Code: "SO", Dial: "+252"}, + {Name: "South Africa", Code: "ZA", Dial: "+27"}, + {Name: "South Georgia and the South Sandwich Islands", Code: "GS", Dial: "+500"}, + {Name: "South Sudan", Code: "SS", Dial: "+211"}, + {Name: "Spain", Code: "ES", Dial: "+34"}, + {Name: "Sri Lanka", Code: "LK", Dial: "+94"}, + {Name: "Sudan", Code: "SD", Dial: "+249"}, + {Name: "Suriname", Code: "SR", Dial: "+597"}, + {Name: "Svalbard and Jan Mayen", Code: "SJ", Dial: "+47"}, + {Name: "Swaziland", Code: "SZ", Dial: "+268"}, + {Name: "Sweden", Code: "SE", Dial: "+46"}, + {Name: "Switzerland", Code: "CH", Dial: "+41"}, + {Name: "Syrian Arab Republic", Code: "SY", Dial: "+963"}, + {Name: "Taiwan", Code: "TW", Dial: "+886"}, + {Name: "Tajikistan", Code: "TJ", Dial: "+992"}, + {Name: "Tanzania, United Republic of Tanzania", Code: "TZ", Dial: "+255"}, + {Name: "Thailand", Code: "TH", Dial: "+66"}, + {Name: "Timor-Leste", Code: "TL", Dial: "+670"}, + {Name: "Togo", Code: "TG", Dial: "+228"}, + {Name: "Tokelau", Code: "TK", Dial: "+690"}, + {Name: "Tonga", Code: "TO", Dial: "+676"}, + {Name: "Trinidad and Tobago", Code: "TT", Dial: "+1868"}, + {Name: "Tunisia", Code: "TN", Dial: "+216"}, + {Name: "Turkey", Code: "TR", Dial: "+90"}, + {Name: "Turkmenistan", Code: "TM", Dial: "+993"}, + {Name: "Turks and Caicos Islands", Code: "TC", Dial: "+1649"}, + {Name: "Tuvalu", Code: "TV", Dial: "+688"}, + {Name: "Uganda", Code: "UG", Dial: "+256"}, + {Name: "Ukraine", Code: "UA", Dial: "+380"}, + {Name: "United Arab Emirates", Code: "AE", Dial: "+971"}, + {Name: "United Kingdom", Code: "GB", Dial: "+44"}, + {Name: "United States", Code: "US", Dial: "+1"}, + {Name: "Uruguay", Code: "UY", Dial: "+598"}, + {Name: "Uzbekistan", Code: "UZ", Dial: "+998"}, + {Name: "Vanuatu", Code: "VU", Dial: "+678"}, + {Name: "Venezuela, Bolivarian Republic of Venezuela", Code: "VE", Dial: "+58"}, + {Name: "Vietnam", Code: "VN", Dial: "+84"}, + {Name: "Virgin Islands, British", Code: "VG", Dial: "+1284"}, + {Name: "Virgin Islands, U.S.", Code: "VI", Dial: "+1340"}, + {Name: "Wallis and Futuna", Code: "WF", Dial: "+681"}, + {Name: "Yemen", Code: "YE", Dial: "+967"}, + {Name: "Zambia", Code: "ZM", Dial: "+260"}, + {Name: "Zimbabwe", Code: "ZW", Dial: "+263"}, +} diff --git a/dategrid_alignment_test.go b/dategrid_alignment_test.go new file mode 100644 index 0000000..abbe9f6 --- /dev/null +++ b/dategrid_alignment_test.go @@ -0,0 +1,208 @@ +package main + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" +) + +// These tests pin the calendar-grid layout contract, written against the +// second bidbuddy.com.au report (2026-06-10, 20:59): the grid renders fixed +// Sun–Sat column headers but poured consecutive day buttons in from column 1 +// with no offset, so June 10 (a Wednesday) sat under "Sun" and tapping the +// cell under "Tue" (showing 12) selected Friday, June 12. The first date must +// be preceded by int(weekday) spacer cells so every button lands under its +// real weekday header. "Today" (window start + IsPast/IsToday flags) must be +// resolved in the visitor/site timezone, not the server clock's. + +// divergentZone returns an IANA zone whose current calendar date differs from +// the server-local date right now. Pacific/Kiritimati (UTC+14) and +// Pacific/Pago_Pago (UTC-11) are 25 hours apart, so at any instant at least +// one of them is on a different date than the server. +func divergentZone(t *testing.T) (*time.Location, string) { + t.Helper() + serverToday := time.Now().Format("2006-01-02") + for _, name := range []string{"Pacific/Kiritimati", "Pacific/Pago_Pago"} { + loc, err := time.LoadLocation(name) + if err != nil { + t.Fatalf("LoadLocation(%s): %v", name, err) + } + if time.Now().In(loc).Format("2006-01-02") != serverToday { + return loc, name + } + } + t.Fatal("no zone diverges from server-local date — zones are 25h apart, impossible") + return nil, "" +} + +// firstDateParam extracts the YYYY-MM-DD value of the first date= query param +// in rendered HTML — i.e. the first day button of the grid. +func firstDateParam(t *testing.T, html string) string { + t.Helper() + i := strings.Index(html, "date=") + if i < 0 || i+15 > len(html) { + t.Fatalf("no date= param found in rendered HTML") + } + return html[i+5 : i+15] +} + +func TestLeadingBlanks(t *testing.T) { + cell := func(date string) DateCell { return DateCell{Date: date} } + cases := []struct { + name string + dates []DateCell + want int + }{ + {"wednesday start needs 3 pads", []DateCell{cell("2026-06-10")}, 3}, + {"sunday start needs none", []DateCell{cell("2026-06-14")}, 0}, + {"saturday start needs 6", []DateCell{cell("2026-06-13")}, 6}, + {"empty grid", nil, 0}, + {"unparseable date", []DateCell{cell("garbage")}, 0}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := leadingBlanks(tc.dates); got != tc.want { + t.Errorf("leadingBlanks(%v) = %d, want %d", tc.dates, got, tc.want) + } + }) + } +} + +// TestHandleGetDateGrid_AlignsFirstDateToWeekdayColumn drives /date-grid with +// a future Wednesday weekStart and asserts exactly 3 spacer cells render +// before the first day button, so the 7-column grid lines dates up with the +// Sun–Sat headers. +func TestHandleGetDateGrid_AlignsFirstDateToWeekdayColumn(t *testing.T) { + router := newTestRouter(t) + + // Next Wednesday at least a week out, so it is never clamped to today. + start := time.Now().AddDate(0, 0, 7) + for start.Weekday() != time.Wednesday { + start = start.AddDate(0, 0, 1) + } + req := httptest.NewRequest(http.MethodGet, + "/date-grid?blockId=b-align&username=alice&eventType=30min&weeks=2&weekStart="+start.Format("2006-01-02"), nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + body := rec.Body.String() + + if got := strings.Count(body, "calcom-date-pad"); got != 3 { + t.Errorf("Wednesday window: want 3 leading pad cells, got %d", got) + } + padIdx := strings.Index(body, "calcom-date-pad") + btnIdx := strings.Index(body, "date=") + if padIdx < 0 || btnIdx < 0 || padIdx > btnIdx { + t.Errorf("pad cells must precede the first day button (padIdx=%d, btnIdx=%d)", padIdx, btnIdx) + } + if !strings.Contains(body, `calcom-date-pad" aria-hidden="true"`) { + t.Errorf("pad cells must be aria-hidden") + } +} + +// TestHandleGetDateGrid_SundayStartHasNoPads is the zero case: a window +// starting on a Sunday begins flush in column 1. +func TestHandleGetDateGrid_SundayStartHasNoPads(t *testing.T) { + router := newTestRouter(t) + + start := time.Now().AddDate(0, 0, 7) + for start.Weekday() != time.Sunday { + start = start.AddDate(0, 0, 1) + } + req := httptest.NewRequest(http.MethodGet, + "/date-grid?blockId=b-sun&username=alice&eventType=30min&weeks=2&weekStart="+start.Format("2006-01-02"), nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if got := strings.Count(rec.Body.String(), "calcom-date-pad"); got != 0 { + t.Errorf("Sunday window: want 0 pad cells, got %d", got) + } +} + +// TestHandleGetDateGrid_TodayResolvedInEventTimezone pins the clamp rule to +// the MEETING's calendar, not the server's: a past weekStart must clamp to +// today in the event's schedule timezone. Pre-fix the handler clamped to the +// server-local date, so a UTC container made a Perth schedule's grid start on +// yesterday until 08:00 AWST. +func TestHandleGetDateGrid_TodayResolvedInEventTimezone(t *testing.T) { + loc, name := divergentZone(t) + fakeCalcom(t, name, nil) + h, _ := newKeyedHandler(t) + wantToday := time.Now().In(loc).Format("2006-01-02") + + req := httptest.NewRequest(http.MethodGet, + "/date-grid?blockId=b-tz&username=alice&eventType=30min&weeks=2&weekStart=2020-01-01", nil) + rec := httptest.NewRecorder() + h.HandleGetDateGrid(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + if got := firstDateParam(t, rec.Body.String()); got != wantToday { + t.Errorf("clamped window must start at today in %s (%s), got %s", name, wantToday, got) + } +} + +// TestCalcomBookingFunc_InitialGridUsesSiteTimezone covers the initial +// server-side block render with a cold event-timezone cache: the window must +// start at today in the SITE timezone (Site Settings → Localization), +// resolved via the settings manager stashed at router-mount time — not at +// the server clock's date. Page render never calls Cal.com. +func TestCalcomBookingFunc_InitialGridUsesSiteTimezone(t *testing.T) { + s, q := newTestSettings() + loc, name := divergentZone(t) + seedSiteTimezone(q, name) + renderSettings = s + resetEventTZCache() + t.Cleanup(func() { renderSettings = nil }) + + html := CalcomBookingFunc(context.Background(), map[string]any{ + "_block_id": "b-init", + "username": "alice", + "eventTypeSlug": "30min", + }) + + wantToday := time.Now().In(loc).Format("2006-01-02") + if got := firstDateParam(t, html); got != wantToday { + t.Errorf("initial grid must start at today in site tz %s (%s), got %s", name, wantToday, got) + } + wantPads := int(time.Now().In(loc).Weekday()) + if got := strings.Count(html, "calcom-date-pad"); got != wantPads { + t.Errorf("initial grid: want %d pad cells for %s, got %d", wantPads, wantToday, got) + } +} + +// TestCalcomBookingFunc_InitialGridPrefersCachedEventTimezone asserts the +// initial render uses the meeting timezone over the site setting once the +// cache is warm (any visitor's first HTMX interaction warms it). +func TestCalcomBookingFunc_InitialGridPrefersCachedEventTimezone(t *testing.T) { + s, q := newTestSettings() + loc, name := divergentZone(t) + seedSiteTimezone(q, "UTC") // site setting present but unset-equivalent + storeEventTZ("alice", "30min", name) + renderSettings = s + t.Cleanup(func() { + renderSettings = nil + resetEventTZCache() + }) + + html := CalcomBookingFunc(context.Background(), map[string]any{ + "_block_id": "b-init2", + "username": "alice", + "eventTypeSlug": "30min", + }) + + wantToday := time.Now().In(loc).Format("2006-01-02") + if got := firstDateParam(t, html); got != wantToday { + t.Errorf("initial grid must start at today in cached event tz %s (%s), got %s", name, wantToday, got) + } + if !strings.Contains(html, name) { + t.Errorf("ShowTimezone label should carry the event tz %s", name) + } +} diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..119aa1c --- /dev/null +++ b/errors.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + "log" + "net/http" +) + +// writeInternalServerError logs err and returns a generic 500 to the client. +// It never exposes err.Error() to the HTTP response. +func writeInternalServerError(w http.ResponseWriter, err error, context string) { + writeServerError(w, err, "Internal Server Error", context) +} + +// writeServerError logs err and returns publicMessage with HTTP 500 to the client. +// It never exposes err.Error() to the HTTP response. +func writeServerError(w http.ResponseWriter, err error, publicMessage string, context string) { + if err != nil && context != "" { + err = fmt.Errorf("%s: %w", context, err) + } + if err != nil { + log.Printf("server error: %v", err) + } + http.Error(w, publicMessage, http.StatusInternalServerError) +} diff --git a/field_routing_test.go b/field_routing_test.go new file mode 100644 index 0000000..90765a7 --- /dev/null +++ b/field_routing_test.go @@ -0,0 +1,541 @@ +package main + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" +) + +// --------------------------------------------------------------------------- +// Cal.com field-taxonomy regressions +// +// Cal.com event types return a mix of SYSTEM booking fields (name, email, +// attendeePhoneNumber, location, notes, guests, title, rescheduleReason) and +// CUSTOM fields. Each system field has a fixed slug + type + `editable` value, +// and reschedule-only fields carry views:[{id:"reschedule"}] (see Cal.com +// getBookingFields.ts). The widget must: +// - NOT render reschedule-only fields on a NEW booking (rescheduleReason) +// - route each field to its correct slot in POST /v2/bookings: +// phone-typed -> attendee.phoneNumber (E.164-normalized) +// guests -> top-level guests[] array +// custom -> bookingFieldsResponses (custom-only per the v2 schema) +// - never emit empty-string responses (Cal.com rejects empty/mistyped values) +// These tests pin that contract. +// --------------------------------------------------------------------------- + +// TestHandleGetForm_DropsRescheduleOnlyFields asserts the booking form does not +// render fields whose Cal.com `views` restricts them to the reschedule flow +// (rescheduleReason). Genuine custom fields (birthday) must still render. +func TestHandleGetForm_DropsRescheduleOnlyFields(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = io.WriteString(w, `{ + "status":"success", + "data":[{ + "id":7,"slug":"intake","title":"Intake","lengthInMinutes":15, + "bookingFields":[ + {"slug":"name","type":"name","editable":"system","required":true}, + {"slug":"email","type":"email","editable":"system-but-optional","required":true}, + {"slug":"rescheduleReason","type":"textarea","editable":"system-but-optional","required":false,"views":[{"id":"reschedule","label":"Reschedule"}]}, + {"slug":"birthday","type":"text","label":"Birthday","editable":"user","required":false} + ] + }] + }`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + req := httptest.NewRequest(http.MethodGet, + "/form?blockId=b1&start=2026-06-01T10:00:00Z&date=2026-06-01&username=alice&eventType=intake&timezone=UTC", nil) + rec := httptest.NewRecorder() + h.HandleGetForm(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + body := rec.Body.String() + // rescheduleReason is reschedule-only — must NOT appear on a new booking. + if strings.Contains(body, `name="rescheduleReason"`) { + t.Errorf("reschedule-only field rescheduleReason rendered on a new booking form; body: %q", body) + } + if strings.Contains(body, "Reason for reschedule") { + t.Errorf("reschedule-only label 'Reason for reschedule' rendered on a new booking form; body: %q", body) + } + // Genuine custom field must still render. + if !strings.Contains(body, `name="birthday"`) { + t.Errorf("expected custom field name=\"birthday\" in form, got: %q", body) + } +} + +// TestHandleGetForm_RendersPhoneCountryCombobox asserts a Cal.com phone field +// renders the searchable country combobox with the detected country (AU, from +// the Australia/Perth timezone) pre-selected, alongside the tel number input. +func TestHandleGetForm_RendersPhoneCountryCombobox(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = io.WriteString(w, `{ + "status":"success", + "data":[{ + "id":7,"slug":"intake","title":"Intake","lengthInMinutes":15, + "bookingFields":[ + {"slug":"name","type":"name","required":true}, + {"slug":"attendeePhoneNumber","type":"phone","required":true} + ] + }] + }`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + req := httptest.NewRequest(http.MethodGet, + "/form?blockId=b1&start=2026-06-01T10:00:00Z&date=2026-06-01&username=alice&eventType=intake&timezone=Australia/Perth&locale=en-AU", nil) + rec := httptest.NewRecorder() + h.HandleGetForm(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + body := rec.Body.String() + for _, want := range []string{ + "data-cc-combobox", + `name="phoneCountry"`, + `value="AU"`, // detected default pre-selected in the hidden input + "🇦🇺 Australia (+61)", // default search label / AU option + `data-code="US"`, // other countries present in the list + `type="tel"`, // the national-number input + `name="attendeePhoneNumber"`, + } { + if !strings.Contains(body, want) { + t.Errorf("phone combobox missing %q in rendered form", want) + } + } +} + +// TestHandleGetForm_DropsSystemFieldsBySlug asserts system fields with no valid +// new-booking home — location (radioInput) and rescheduleReason even WITHOUT a +// views flag — are not rendered (their submitted values would be discarded). +// Genuine custom fields still render. +func TestHandleGetForm_DropsSystemFieldsBySlug(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = io.WriteString(w, `{ + "status":"success", + "data":[{ + "id":7,"slug":"intake","title":"Intake","lengthInMinutes":15, + "bookingFields":[ + {"slug":"name","type":"name","required":true}, + {"slug":"location","type":"radioInput","editable":"system","required":false}, + {"slug":"rescheduleReason","type":"textarea","editable":"system-but-optional","required":false}, + {"slug":"birthday","type":"text","label":"Birthday","editable":"user","required":false} + ] + }] + }`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + req := httptest.NewRequest(http.MethodGet, + "/form?blockId=b1&start=2026-06-01T10:00:00Z&date=2026-06-01&username=alice&eventType=intake&timezone=UTC", nil) + rec := httptest.NewRecorder() + h.HandleGetForm(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + body := rec.Body.String() + if strings.Contains(body, `name="location"`) { + t.Errorf("location field must not render (its value is dropped on submit); body: %q", body) + } + if strings.Contains(body, `name="rescheduleReason"`) { + t.Errorf("rescheduleReason must not render even without a views flag; body: %q", body) + } + if !strings.Contains(body, `name="birthday"`) { + t.Errorf("expected custom field name=\"birthday\" to still render") + } +} + +// TestHandleCreateBooking_EmptySystemFieldsNotSent asserts the real-world +// failure case: the visitor fills name/email but leaves the optional system +// fields (location, notes, guests, rescheduleReason, title) empty. None of +// those empties may reach Cal.com — Cal.com rejects empty/mistyped system +// responses with a 400, which is the production booking failure. +func TestHandleCreateBooking_EmptySystemFieldsNotSent(t *testing.T) { + cap := &capturedBooking{} + fake := httptest.NewServer(cap.handler(t)) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-06-01T10:00:00Z"}, + "name": {"Bob"}, + "email": {"bob@example.com"}, + "timezone": {"America/Toronto"}, + "attendeePhoneNumber": {""}, + "location": {""}, + "notes": {""}, + "guests": {""}, + "rescheduleReason": {""}, + "title": {""}, + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + + cap.mu.Lock() + defer cap.mu.Unlock() + if cap.body == nil { + t.Fatalf("Cal.com received no parseable body") + } + + // bookingFieldsResponses must be omitted entirely when there is nothing + // custom and non-empty to send. + if resps, present := cap.body["bookingFieldsResponses"]; present { + m, _ := resps.(map[string]any) + if len(m) != 0 { + t.Errorf("bookingFieldsResponses must be empty/omitted when all optional fields are blank, got: %v", resps) + } + // No empty-string values may ever be sent. + for k, v := range m { + if s, ok := v.(string); ok && strings.TrimSpace(s) == "" { + t.Errorf("bookingFieldsResponses[%q] is an empty string — Cal.com rejects empty responses", k) + } + } + // System/reschedule keys must never appear as custom responses. + for _, banned := range []string{"location", "notes", "guests", "rescheduleReason", "title", "attendeePhoneNumber"} { + if _, bad := m[banned]; bad { + t.Errorf("bookingFieldsResponses must not contain system key %q", banned) + } + } + } + // guests is a top-level field — empty guests must be omitted, not sent as "". + if g, present := cap.body["guests"]; present { + t.Errorf("empty guests must be omitted from the top-level payload, got: %v", g) + } +} + +// TestHandleCreateBooking_GuestsRoutedToTopLevelArray asserts that the `guests` +// system field (Cal.com type multiemail) is split and sent as the top-level +// `guests` array of emails — NOT as a comma-joined string inside +// bookingFieldsResponses. +func TestHandleCreateBooking_GuestsRoutedToTopLevelArray(t *testing.T) { + cap := &capturedBooking{} + fake := httptest.NewServer(cap.handler(t)) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-06-01T10:00:00Z"}, + "name": {"Bob"}, + "email": {"bob@example.com"}, + "guests": {"a@x.com, b@y.com"}, + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + + cap.mu.Lock() + defer cap.mu.Unlock() + guests, ok := cap.body["guests"].([]any) + if !ok { + t.Fatalf("guests must be a top-level JSON array, got %T: %v", cap.body["guests"], cap.body["guests"]) + } + if len(guests) != 2 || guests[0] != "a@x.com" || guests[1] != "b@y.com" { + t.Errorf("guests array wrong: %v", guests) + } + if resps, ok := cap.body["bookingFieldsResponses"].(map[string]any); ok { + if _, bad := resps["guests"]; bad { + t.Errorf("guests must not appear in bookingFieldsResponses") + } + } +} + +// TestHandleCreateBooking_PhoneSlugRoutedToAttendee asserts that a phone value +// submitted under Cal.com's system slug `attendeePhoneNumber` (not the literal +// "phone") lands in attendee.phoneNumber, E.164-normalized (separators +// stripped) — not in bookingFieldsResponses. +func TestHandleCreateBooking_PhoneSlugRoutedToAttendee(t *testing.T) { + cap := &capturedBooking{} + fake := httptest.NewServer(cap.handler(t)) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-06-01T10:00:00Z"}, + "name": {"Bob"}, + "email": {"bob@example.com"}, + "attendeePhoneNumber": {"+61 432 777 227"}, + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + + cap.mu.Lock() + defer cap.mu.Unlock() + att, ok := cap.body["attendee"].(map[string]any) + if !ok { + t.Fatalf("attendee missing/wrong type: %v", cap.body) + } + if att["phoneNumber"] != "+61432777227" { + t.Errorf("attendee.phoneNumber: got %v, want +61432777227 (separators stripped)", att["phoneNumber"]) + } + if resps, ok := cap.body["bookingFieldsResponses"].(map[string]any); ok { + if _, bad := resps["attendeePhoneNumber"]; bad { + t.Errorf("attendeePhoneNumber must route to attendee.phoneNumber, not bookingFieldsResponses") + } + } +} + +// TestNormalizePhone_AppliesDialCode pins the E.164 normalization: Cal.com +// rejects national-format numbers (`{attendeePhoneNumber}invalid_number`), so a +// configured default dial code converts trunk-0 / bare-national numbers to +// international form. Numbers already in +/00 international form are left intact. +func TestNormalizePhone(t *testing.T) { + // libphonenumber applies each country's trunk rules. The IN/IT/RU cases failed + // under the old hand-rolled heuristic — the reviewer's bug class. + cases := []struct{ raw, region, want string }{ + {"0432777227", "AU", "+61432777227"}, // AU mobile, trunk-0 dropped + {"0412 345 678", "AU", "+61412345678"}, // separators + trunk-0 + {"07911123456", "GB", "+447911123456"}, // UK mobile, trunk-0 dropped + {"+44 7911 123456", "", "+447911123456"}, // already international, region ignored + {"9876543210", "IN", "+919876543210"}, // India mobile + {"0236618300", "IT", "+390236618300"}, // Italy landline KEEPS the leading 0 + {"89161234567", "RU", "+79161234567"}, // Russia trunk prefix is 8 + {"", "AU", ""}, // empty stays empty + {"NOPE", "AU", "NOPE"}, // unparseable -> best-effort fallback + {"555-1212", "", "5551212"}, // no region, not international -> fallback strip + } + for _, c := range cases { + if got := normalizePhone(c.raw, c.region); got != c.want { + t.Errorf("normalizePhone(%q, %q) = %q, want %q", c.raw, c.region, got, c.want) + } + } +} + +// TestRegionFromLocale pins extraction of the region subtag from navigator.language. +func TestRegionFromLocale(t *testing.T) { + cases := map[string]string{ + "en-AU": "AU", + "en_AU": "AU", + "AU": "AU", + "fr-FR": "FR", + "zh-Hans-CN": "CN", + "de-DE-1996": "DE", + "en": "", // no region subtag + "": "", + } + for in, want := range cases { + if got := regionFromLocale(in); got != want { + t.Errorf("regionFromLocale(%q) = %q, want %q", in, got, want) + } + } +} + +// TestResolveRegion pins the country pre-selected in the phone combobox: +// timezone region first (strongest locator), then browser locale region. +func TestResolveRegion(t *testing.T) { + cases := []struct{ locale, tz, want string }{ + {"en-AU", "", "AU"}, + {"en-US", "Australia/Sydney", "AU"}, // timezone beats mismatched locale + {"fr-FR", "", "FR"}, + {"", "Pacific/Auckland", "NZ"}, + {"en", "", ""}, // no region + {"", "UTC", ""}, // unmapped tz, no locale + {"", "", ""}, + } + for _, c := range cases { + if got := resolveRegion(c.locale, c.tz); got != c.want { + t.Errorf("resolveRegion(%q, %q) = %q, want %q", c.locale, c.tz, got, c.want) + } + } +} + +// TestCountryFlag verifies ISO code → regional-indicator emoji. +func TestCountryFlag(t *testing.T) { + if got := countryFlag("AU"); got != "🇦🇺" { + t.Errorf("countryFlag(AU) = %q, want 🇦🇺", got) + } + // Guard rejects malformed input (wrong length / non-letters), not merely + // unknown-but-well-formed codes. + if got := countryFlag("X"); got != "" { + t.Errorf("countryFlag(too short) = %q, want empty", got) + } + if got := countryFlag("1A"); got != "" { + t.Errorf("countryFlag(non-letters) = %q, want empty", got) + } + if got := countryLabel("AU"); got != "🇦🇺 Australia (+61)" { + t.Errorf("countryLabel(AU) = %q, want '🇦🇺 Australia (+61)'", got) + } +} + +// TestHandleCreateBooking_PhoneCountrySelectionWins asserts the visitor's +// explicit country-combobox choice drives the E.164 conversion and overrides +// browser detection. +func TestHandleCreateBooking_PhoneCountrySelectionWins(t *testing.T) { + cap := &capturedBooking{} + fake := httptest.NewServer(cap.handler(t)) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{ + "blockId": {"b1"}, + "username": {"bidbuddy"}, + "eventType": {"30min"}, + "start": {"2026-06-01T09:30:00Z"}, + "name": {"Alex"}, + "email": {"alex@example.com"}, + "attendeePhoneNumber": {"0432777227"}, + "phoneCountry": {"AU"}, // explicit selection + "locale": {"en-US"}, // mismatched locale that must NOT win + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + cap.mu.Lock() + defer cap.mu.Unlock() + att, _ := cap.body["attendee"].(map[string]any) + if att["phoneNumber"] != "+61432777227" { + t.Errorf("attendee.phoneNumber: got %v, want +61432777227 (explicit AU selection)", att["phoneNumber"]) + } + // phoneCountry is a control field — must not leak into responses. + if resps, ok := cap.body["bookingFieldsResponses"].(map[string]any); ok { + if _, bad := resps["phoneCountry"]; bad { + t.Errorf("phoneCountry must not appear in bookingFieldsResponses") + } + } +} + +// TestHandleCreateBooking_InfersDialCodeFromLocale is the zero-config regression: +// the bidbuddy visitor submits the AU national number 0432777227 with NO admin +// dial code configured; the browser locale (en-AU) must drive the E.164 +// conversion so Cal.com accepts attendee.phoneNumber. +func TestHandleCreateBooking_InfersDialCodeFromLocale(t *testing.T) { + cap := &capturedBooking{} + fake := httptest.NewServer(cap.handler(t)) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + // Deliberately NO SetDefaultDialCode — detection must come from the locale. + + form := url.Values{ + "blockId": {"b1"}, + "username": {"bidbuddy"}, + "eventType": {"30min"}, + "start": {"2026-06-01T09:30:00Z"}, + "name": {"Alex Dunmow"}, + "email": {"alex@example.com"}, + "attendeePhoneNumber": {"0432777227"}, + "locale": {"en-AU"}, + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + cap.mu.Lock() + defer cap.mu.Unlock() + att, ok := cap.body["attendee"].(map[string]any) + if !ok { + t.Fatalf("attendee missing/wrong type: %v", cap.body) + } + if att["phoneNumber"] != "+61432777227" { + t.Errorf("attendee.phoneNumber: got %v, want +61432777227 (E.164 inferred from en-AU locale)", att["phoneNumber"]) + } + // locale is a control field — it must never leak into bookingFieldsResponses. + if resps, ok := cap.body["bookingFieldsResponses"].(map[string]any); ok { + if _, bad := resps["locale"]; bad { + t.Errorf("locale must not appear in bookingFieldsResponses") + } + } +} + +// TestStatusErrorWithBody_ParsesNestedCalcomError guards the diagnostic gap: +// Cal.com v2 returns errors as a NESTED object {"error":{"code","message"}}, +// not a flat {"message"}. statusErrorWithBody must surface that message in the +// (server-side) error so booking failures are diagnosable instead of a bare +// "status 400". +func TestStatusErrorWithBody_ParsesNestedCalcomError(t *testing.T) { + body := []byte(`{"status":"error","timestamp":"2026-05-30T00:00:00Z","path":"/v2/bookings","error":{"code":"BAD_REQUEST","message":"Invalid responses - guests: Expected array, received string"}}`) + err := statusErrorWithBody("booking", http.StatusBadRequest, body) + if err == nil { + t.Fatal("expected an error") + } + msg := err.Error() + if !strings.Contains(msg, "400") { + t.Errorf("error must include the status code, got: %q", msg) + } + if !strings.Contains(msg, "guests: Expected array") { + t.Errorf("error must surface Cal.com's nested error.message, got: %q", msg) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1ca2c05 --- /dev/null +++ b/go.mod @@ -0,0 +1,22 @@ +module git.dev.alexdunmow.com/block/calcomblock + +go 1.26.4 + +require ( + git.dev.alexdunmow.com/block/core v0.18.2 + github.com/a-h/templ v0.3.1020 + github.com/go-chi/chi/v5 v5.3.0 + github.com/google/uuid v1.6.0 + github.com/jackc/pgx/v5 v5.9.2 + github.com/nyaruka/phonenumbers v1.8.0 +) + +require ( + connectrpc.com/connect v1.20.0 // indirect + github.com/BurntSushi/toml v1.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + golang.org/x/mod v0.34.0 // indirect + golang.org/x/text v0.36.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7f05842 --- /dev/null +++ b/go.sum @@ -0,0 +1,46 @@ +connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ= +connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4= +git.dev.alexdunmow.com/block/core v0.18.2 h1:+3OfZ424yoc1k1CucXYARk8TBkh8Z693HRkhf5Ci2wU= +git.dev.alexdunmow.com/block/core v0.18.2/go.mod h1:GGuUu826AoJepC/hKLGJ7BX3PQaDss9ueCT0se6Ao2w= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw= +github.com/a-h/templ v0.3.1020/go.mod h1:A2DlK61v+K+NRoGnhmYbNYVmtYHcFO5/AisMvBdDxTM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM= +github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= +github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/nyaruka/phonenumbers v1.8.0 h1:TrXNJmbwcAHajzDqin3mLWw57vqLUA6ZjVdeNds0heQ= +github.com/nyaruka/phonenumbers v1.8.0/go.mod h1:fsKPJ70O9JetEA4ggnJadYTFWwtGPvu/lETTXNXq6Cs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handler.go b/handler.go new file mode 100644 index 0000000..b4a93d5 --- /dev/null +++ b/handler.go @@ -0,0 +1,1150 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "log" + "log/slog" + "net/http" + "net/url" + "regexp" + "sort" + "strconv" + "strings" + "sync" + "time" + + "git.dev.alexdunmow.com/block/core/auth" + "git.dev.alexdunmow.com/block/core/captcha" + "git.dev.alexdunmow.com/block/core/plugin" + "git.dev.alexdunmow.com/block/core/rbac" + "git.dev.alexdunmow.com/block/calcomblock/internal/helpers" + "github.com/go-chi/chi/v5" + "github.com/nyaruka/phonenumbers" +) + +// captchaVerifier is the shared Cap (trycap.dev) proof-of-work verifier, built +// once from the per-instance secret and injected at server startup via +// SetCaptchaServer — mirroring SetNotifyFn. It is nil when the instance captcha +// secret failed to load: an enabled booking block then FAILS CLOSED (rejects +// rather than allowing an unverified booking through). Read directly by +// HandleCreateBooking; the mailing-list / block-form handlers hold the same +// server on their struct, but the plugin HTTP mount is generic so a package +// seam is the established injection point here. +var captchaVerifier *captcha.Server + +// SetCaptchaServer wires the CMS's shared captcha server into the Cal.com +// booking handler. Called once at server startup from server.go. Safe to pass +// nil (captcha then unavailable → enabled blocks fail closed). +func SetCaptchaServer(s *captcha.Server) { captchaVerifier = s } + +// blockContentResolver answers the server-authoritative question "does a +// booking for this Cal.com username + event-type slug require a captcha token?" +// by scanning currently-published block content — NEVER a client-supplied +// blockId. *poolQuerier satisfies it in production (per-call transaction over +// plugin.Pool); tests inject a fake. Nil in contexts without a DB (editor +// preview, direct-handler unit tests) — bookingRequiresCaptcha then reports +// false (baseline honeypot + rate limit still apply). +type blockContentResolver interface { + CalcomBookingRequiresCaptcha(ctx context.Context, username, eventTypeSlug string) (bool, error) +} + +// requireAdmin gates a sub-router to authenticated CMS admins. +// The outer server runs OptionalAuth which populates the context with +// auth.Claims when the request carries a valid admin session cookie or +// Bearer token; otherwise context is empty. Plugin admin endpoints are +// otherwise publicly mounted at /api/plugins/calcomblock/*, so this guard +// is the only thing standing between the open internet and the tenant's +// Cal.com API key / webhook secret. +func requireAdmin(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + claims, ok := auth.GetUserFromContext(r.Context()) + if !ok || claims == nil { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + if !rbac.HasPermission(rbac.Role(claims.Role), rbac.RoleAdmin) { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + next.ServeHTTP(w, r) + }) +} + +// emailRE is the relaxed email validation pattern used as a cheap filter for +// obvious junk. Cal.com does its own validation server-side. +var emailRE = regexp.MustCompile(`^[^@\s]+@[^@\s]+\.[^@\s]+$`) + +// bookingsPerHourPerIP is the rate-limit ceiling for the public /book endpoint. +const bookingsPerHourPerIP = 10 + +// controlFields are transport / explicitly-handled form values (attendee +// name+email, hidden routing inputs, the honeypot). They are never forwarded as +// Cal.com bookingFieldsResponses. +var controlFields = map[string]bool{ + "blockId": true, + "username": true, + "eventType": true, + "start": true, + "timezone": true, + "locale": true, // browser locale, used server-side to infer the phone dial code + "phoneCountry": true, // country-combobox selection (ISO code), drives the phone dial code + "bn_message_extra": true, // honeypot (deliberately non-semantic to dodge Chrome autofill) + "name": true, + "email": true, + "cap-token": true, // captcha verification token (server-read, never forwarded to Cal.com) +} + +// phoneFieldSlugs are the Cal.com system slugs that carry the attendee phone +// number. The default phone field's slug is `attendeePhoneNumber` (NOT "phone"), +// so a value under any of these routes to attendee.phoneNumber — never to +// bookingFieldsResponses, where it would both fail phone validation and orphan +// the attendee's number. +var phoneFieldSlugs = map[string]bool{ + "attendeePhoneNumber": true, + "phone": true, + "smsReminderNumber": true, + "number": true, +} + +// systemDropSlugs are Cal.com system fields with no valid home in a new-booking +// bookingFieldsResponses payload (custom-field-only per the v2 schema): +// - location: a structured top-level object; left unset so Cal.com uses the +// event type's configured location. +// - rescheduleReason: reschedule-only — invalid on a create. +// - title: system-managed. +// +// `guests` is handled separately (top-level array); `name`/`email` are read as +// attendee fields via controlFields. +var systemDropSlugs = map[string]bool{ + "location": true, + "rescheduleReason": true, + "title": true, +} + +// bookingFormPayload is the Cal.com-bound projection of a submitted booking form. +type bookingFormPayload struct { + Phone string // -> attendee.phoneNumber (E.164-normalized) + Guests []string // -> top-level guests[] + Responses map[string]any // -> bookingFieldsResponses (custom fields only) +} + +// routeBookingForm splits a submitted booking form into Cal.com's typed slots +// per the v2 POST /bookings schema. Phone-typed fields go to attendee.phoneNumber, +// `guests` becomes the top-level email array, and only genuine custom fields land +// in bookingFieldsResponses. Empty values are dropped entirely — Cal.com rejects +// empty/mistyped system responses, which is the root cause of the silent booking +// failures this routing fixes. region (an ISO 3166-1 alpha-2 code, e.g. "AU") is +// used to convert national-format attendee phone numbers to E.164. +func routeBookingForm(form url.Values, region string) bookingFormPayload { + out := bookingFormPayload{Responses: map[string]any{}} + for k, vs := range form { + switch { + case controlFields[k]: + // attendee name/email + transport fields handled elsewhere + case phoneFieldSlugs[k]: + if out.Phone == "" { + out.Phone = normalizePhone(firstNonEmpty(vs), region) + } + case k == "guests": + out.Guests = append(out.Guests, splitGuestEmails(vs)...) + case systemDropSlugs[k]: + // no valid custom-response home; dropped + default: + // Custom field. Drop empties; collapse to a scalar or a JSON array + // for multi-valued inputs (checkboxes / multiselect). + switch nonEmpty := dropEmptyValues(vs); len(nonEmpty) { + case 0: + case 1: + out.Responses[k] = nonEmpty[0] + default: + out.Responses[k] = toAnySlice(nonEmpty) + } + } + } + if len(out.Responses) == 0 { + out.Responses = nil + } + if len(out.Guests) == 0 { + out.Guests = nil + } + return out +} + +// normalizePhone converts a submitted phone number to the E.164 form Cal.com +// requires (it rejects national-format numbers with `{attendeePhoneNumber} +// invalid_number`). It parses via libphonenumber using `region` (an ISO 3166-1 +// alpha-2 code, e.g. "AU") as the default country, which correctly applies each +// country's trunk-prefix rules — Italy keeps its leading 0, Russia's trunk is 8, +// most others drop a leading 0. Numbers already in international form (leading +// "+") parse regardless of region. When parsing fails or the number is invalid +// (unknown region, partial input) the separator-stripped input is returned +// best-effort and Cal.com does its own validation. Empty input yields "". +func normalizePhone(raw, region string) string { + if strings.TrimSpace(raw) == "" { + return "" + } + region = strings.ToUpper(strings.TrimSpace(region)) + if _, ok := countryByCode(region); !ok { + region = "" // unknown region; libphonenumber then relies on a leading "+" + } + if num, err := phonenumbers.Parse(raw, region); err == nil && phonenumbers.IsValidNumber(num) { + return phonenumbers.Format(num, phonenumbers.E164) + } + return stripPhoneSeparators(raw) +} + +// stripPhoneSeparators removes spaces (incl. non-breaking), tabs and common +// punctuation used to format phone numbers, leaving digits and a leading "+". +func stripPhoneSeparators(raw string) string { + var b strings.Builder + for _, r := range strings.TrimSpace(raw) { + switch r { + case ' ', ' ', '\t', '-', '(', ')', '.', '/': + // drop separators + default: + b.WriteRune(r) + } + } + return b.String() +} + +// splitGuestEmails flattens Cal.com's multiemail `guests` field — rendered by the +// widget as a single text input — into individual addresses. Accepts values +// separated by commas, semicolons, whitespace or newlines; trims and drops blanks. +func splitGuestEmails(vs []string) []string { + var out []string + for _, v := range vs { + for _, part := range strings.FieldsFunc(v, func(r rune) bool { + return r == ',' || r == ';' || r == '\n' || r == ' ' || r == '\t' + }) { + if p := strings.TrimSpace(part); p != "" { + out = append(out, p) + } + } + } + return out +} + +// firstNonEmpty returns the first trimmed non-empty value, or "". +func firstNonEmpty(vs []string) string { + for _, v := range vs { + if t := strings.TrimSpace(v); t != "" { + return t + } + } + return "" +} + +// dropEmptyValues returns vs with blank / whitespace-only entries removed. +func dropEmptyValues(vs []string) []string { + out := make([]string, 0, len(vs)) + for _, v := range vs { + if strings.TrimSpace(v) != "" { + out = append(out, v) + } + } + return out +} + +// toAnySlice widens a []string to []any for JSON array encoding. +func toAnySlice(ss []string) []any { + out := make([]any, len(ss)) + for i, s := range ss { + out[i] = s + } + return out +} + +// CalcomHandler handles Cal.com booking API endpoints +type CalcomHandler struct { + settings *SettingsManager + limiter *RateLimiter + appURL string + // blockConfig resolves a block's stored content by blockId so captcha + // enforcement reads the server-authoritative captchaEnabled flag rather than + // trusting the POST body. nil in editor-preview / direct-handler unit tests, + // where blockCaptchaEnabled reports false. + blockConfig blockContentResolver +} + +// NewCalcomHandler creates a new Cal.com handler. appURL is the externally +// reachable origin used to render the tenant-specific webhook URL in the +// admin settings panel (e.g. "https://acme.blockninjacms.com"). +func NewCalcomHandler(settings *SettingsManager, limiter *RateLimiter, appURL string) *CalcomHandler { + return &CalcomHandler{settings: settings, limiter: limiter, appURL: appURL} +} + +// NewCalcomRouter creates the HTTP router for the Cal.com plugin. +// deps carries the CMS-provided Crypto and database Pool used to persist +// and encrypt the Cal.com API key + webhook secret. +func NewCalcomRouter(deps plugin.CoreServices) http.Handler { + r := chi.NewRouter() + // Settings persistence goes through the SDK settings capabilities: under the + // wasm sandbox the plugin's Postgres role cannot touch the CMS `settings` + // table (public schema) directly, so the host performs the DB work. + pq := &capabilityQuerier{settings: deps.Settings, updater: deps.SettingsUpdater} + settings := NewSettingsManager(deps.Crypto, pq) + // Routes are mounted at startup, before any page render — stash the + // manager so CalcomBookingFunc (which renders with no request and no + // handler) can resolve the site/event timezone for the initial grid. + renderSettings = settings + limiter := NewRateLimiter(bookingsPerHourPerIP) + helpers.StartCleanupLoop(context.Background(), 15*time.Minute, limiter.SweepStale) + h := NewCalcomHandler(settings, limiter, deps.AppURL) + // blockConfig scans published block content for the server-authoritative + // captcha requirement (CalcomBookingRequiresCaptcha over page_block_snapshots + // in the public schema). No wasm-ABI capability exposes that today, and the + // sandboxed plugin role cannot read it via SQL, so it is left nil: + // bookingRequiresCaptcha then reports false and the baseline honeypot + + // per-IP rate limit still apply. Restoring server-authoritative captcha + // enforcement needs a future core capability that exposes published-block + // content across the wasm ABI (tracked as a follow-up). + h.blockConfig = nil + + // Public booking endpoints — reachable by anonymous visitors. + r.Get("/slots", h.HandleGetSlots) + r.Get("/form", h.HandleGetForm) + r.Post("/book", h.HandleCreateBooking) + r.Post("/cancel", h.HandleCancelBooking) + r.Get("/reset", h.HandleReset) + r.Get("/date-grid", h.HandleGetDateGrid) + + // Webhook receiver — HMAC-verified inside the handler, so no admin gate. + wh := NewWebhookHandler(settings, slog.Default()) + r.Post("/webhook", wh.Handle) + + // Admin endpoints — gated by requireAdmin. The plugin HTTP mount has no + // outer admin middleware, so this is the only line keeping the API key + // + webhook secret + Cal.com proxy out of attackers' hands. + r.Group(func(admin chi.Router) { + admin.Use(requireAdmin) + admin.Get("/settings", h.HandleGetSettings) + admin.Post("/settings", h.HandleSaveSettings) + admin.Post("/settings/rotate-webhook-secret", h.HandleRotateWebhookSecret) + admin.Post("/test", h.HandleTestConnection) + admin.Get("/event-types", h.HandleListEventTypes) + }) + + return r +} + +// eventTZCache memoizes the meeting timezone per (username, event slug) so +// the public endpoints don't pay two Cal.com round-trips per request. 15 +// minutes is short enough that an availability-timezone change propagates +// quickly, long enough to amortize bursts. Failures are not cached — the next +// request retries. Package-level (not per-handler) so CalcomBookingFunc, +// which renders without a handler, can peek warm entries. +var eventTZCache = struct { + sync.Mutex + m map[string]eventTZEntry +}{m: map[string]eventTZEntry{}} + +type eventTZEntry struct { + tz string + fetched time.Time +} + +const eventTZTTL = 15 * time.Minute + +func eventTZKey(username, eventType string) string { return username + "|" + eventType } + +// peekEventTZ returns the cached meeting timezone, or "" when cold/expired. +// Never fetches — safe to call from the page-render path. +func peekEventTZ(username, eventType string) string { + eventTZCache.Lock() + defer eventTZCache.Unlock() + e, ok := eventTZCache.m[eventTZKey(username, eventType)] + if !ok || time.Since(e.fetched) > eventTZTTL { + return "" + } + return e.tz +} + +func storeEventTZ(username, eventType, tz string) { + eventTZCache.Lock() + defer eventTZCache.Unlock() + eventTZCache.m[eventTZKey(username, eventType)] = eventTZEntry{tz: tz, fetched: time.Now()} +} + +// resetEventTZCache empties the cache. Test seam — withCalcomBaseURL calls it +// so per-test fake servers can't leak timezones into each other. +func resetEventTZCache() { + eventTZCache.Lock() + defer eventTZCache.Unlock() + eventTZCache.m = map[string]eventTZEntry{} +} + +// fetchEventTimezone resolves the meeting's timezone from Cal.com: the event +// type's pinned availability schedule when one is set, else the account's +// default schedule. Returns "" when any hop fails — callers then fall down +// the site-setting ladder rather than guessing. +func fetchEventTimezone(ctx context.Context, apiKey, username, eventType string) string { + client := NewCalcomClient(apiKey) + schedules, err := client.ListSchedules(ctx) + if err != nil || len(schedules) == 0 { + log.Printf("calcom: list schedules for %s/%s: %v", username, eventType, err) + return "" + } + var scheduleID *int64 + if et, err := client.GetEventType(ctx, username, eventType); err == nil { + scheduleID = et.ScheduleID + } else { + log.Printf("calcom: get event type %s/%s for timezone: %v (using default schedule)", username, eventType, err) + } + var fallback string + for _, s := range schedules { + if scheduleID != nil && s.ID == *scheduleID { + return s.TimeZone + } + if s.IsDefault && fallback == "" { + fallback = s.TimeZone + } + } + return fallback +} + +// eventLocation resolves the timezone every booking surface renders in: the +// MEETING's timezone (its Cal.com availability schedule), else the site-wide +// Localization setting, else UTC. The visitor's browser timezone is +// deliberately not consulted: availability like "Mon–Fri 8–6" is a fact about +// the host's calendar, and making the display depend on per-visitor client +// state is what produced the bidbuddy incidents (2026-06-10) — cached pages +// shipped a UTC sentinel and visitors saw 12:00 AM slots. One zone, server +// resolved, every surface. +func (h *CalcomHandler) eventLocation(ctx context.Context, apiKey, username, eventType string) (*time.Location, string) { + tz := peekEventTZ(username, eventType) + if tz == "" && apiKey != "" && username != "" && eventType != "" { + if tz = fetchEventTimezone(ctx, apiKey, username, eventType); tz != "" { + storeEventTZ(username, eventType, tz) + } + } + return h.settings.resolveLocation(ctx, tz) +} + +// HandleGetSlots fetches available time slots from Cal.com via CalcomClient. +func (h *CalcomHandler) HandleGetSlots(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + apiKey, err := h.settings.GetAPIKey(r.Context()) + if err != nil { + h.renderError(w, "Failed to load Cal.com settings", "", true) + return + } + if apiKey == "" { + h.renderError(w, "Cal.com integration is not configured", "", true) + return + } + + username := r.URL.Query().Get("username") + eventType := r.URL.Query().Get("eventType") + dateStr := r.URL.Query().Get("date") + blockID := r.URL.Query().Get("blockId") + + if username == "" || eventType == "" || dateStr == "" { + h.renderError(w, "Missing required parameters", blockID, true) + return + } + + loc, tzName := h.eventLocation(r.Context(), apiKey, username, eventType) + + // The day window is midnight-to-midnight in the MEETING's timezone — not + // UTC. Built at UTC midnight, a Perth event's "June 11" window covers + // June 11 8:00 AM – June 12 8:00 AM local, pulling next-day slots under + // the wrong heading. + dayStart, err := time.ParseInLocation("2006-01-02", dateStr, loc) + if err != nil { + h.renderError(w, "Invalid date format", blockID, true) + return + } + dayEnd := dayStart.AddDate(0, 0, 1) + + client := NewCalcomClient(apiKey) + slotsResp, err := client.GetSlots( + r.Context(), + username, + eventType, + dayStart.Format(time.RFC3339), + dayEnd.Format(time.RFC3339), + tzName, + ) + if err != nil { + log.Printf("calcom: get slots: %v", err) + h.renderError(w, "Failed to fetch slots from Cal.com", blockID, true) + return + } + + type timedSlot struct { + at time.Time + slot TimeSlot + } + var timed []timedSlot + for dateKey, timeSlots := range slotsResp.Data { + for _, slot := range timeSlots { + ts := slot.When() + t, err := time.Parse(time.RFC3339, ts) + if err != nil { + continue + } + local := t.In(loc) + // Cal.com's end bound is date-granular: ask for one day and the + // entire end day comes back too. Clamp to the requested local day. + if local.Before(dayStart) || !local.Before(dayEnd) { + continue + } + timed = append(timed, timedSlot{at: local, slot: TimeSlot{ + Start: ts, + DisplayTime: local.Format("3:04 PM"), + DateKey: dateKey, + }}) + } + } + // Slots are assembled by ranging a map — without a sort the order is luck. + sort.Slice(timed, func(i, j int) bool { return timed[i].at.Before(timed[j].at) }) + slots := make([]TimeSlot, 0, len(timed)) + for _, ts := range timed { + slots = append(slots, ts.slot) + } + + config := SlotConfig{ + BlockID: blockID, + Username: username, + EventType: eventType, + SelectedDate: dayStart.Format("Monday, January 2, 2006"), + } + + var buf bytes.Buffer + if err := CalcomTimeSlots(slots, config).Render(r.Context(), &buf); err != nil { + h.renderError(w, "Failed to render slots", blockID, true) + return + } + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if _, err := w.Write(buf.Bytes()); err != nil { + log.Printf("failed to write Cal.com slots response: %v", err) + } +} + +// HandleGetForm renders the booking form, including any custom bookingFields +// configured on the Cal.com event type. +func (h *CalcomHandler) HandleGetForm(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + blockID := r.URL.Query().Get("blockId") + startTime := r.URL.Query().Get("start") + dateStr := r.URL.Query().Get("date") + username := r.URL.Query().Get("username") + eventType := r.URL.Query().Get("eventType") + + apiKey, apiKeyErr := h.settings.GetAPIKey(r.Context()) + loc, tzName := h.eventLocation(r.Context(), apiKey, username, eventType) + + t, err := time.Parse(time.RFC3339, startTime) + displayTime := startTime + if err == nil { + displayTime = t.In(loc).Format("3:04 PM") + } + + displayDate := dateStr + if d, err := time.Parse("2006-01-02", dateStr); err == nil { + displayDate = d.Format("Monday, January 2, 2006") + } + + var bookingFields []BookingField + if apiKeyErr == nil && apiKey != "" && username != "" && eventType != "" { + client := NewCalcomClient(apiKey) + et, etErr := client.GetEventType(r.Context(), username, eventType) + if etErr != nil { + // Fall back to default fields so a transient Cal.com error doesn't + // strand the visitor on a half-rendered form. + log.Printf("calcom: get event type %q/%q: %v", username, eventType, etErr) + } else { + bookingFields = et.BookingFields + } + } + + formConfig := FormConfig{ + BlockID: blockID, + Username: username, + EventType: eventType, + SelectedDate: displayDate, + SelectedTime: displayTime, + StartTime: startTime, + TimeZone: tzName, + BookingFields: bookingFields, + DefaultCountryCode: resolveRegion(r.URL.Query().Get("locale"), tzName), + // Same server-authoritative source HandleCreateBooking enforces against, + // so the widget is shown exactly when a token will be required — keyed on + // the username+eventType, not the client-supplied blockId. + CaptchaEnabled: h.bookingRequiresCaptcha(r.Context(), username, eventType), + } + + var buf bytes.Buffer + if err := CalcomBookingForm(formConfig).Render(r.Context(), &buf); err != nil { + h.renderError(w, "Failed to render form", blockID, false) + return + } + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if _, err := w.Write(buf.Bytes()); err != nil { + log.Printf("failed to write Cal.com booking form response: %v", err) + } +} + +// bookingRequiresCaptcha reports whether a booking for this Cal.com username + +// event-type slug requires a captcha token. The decision is server-authoritative: +// it is true iff ANY currently-published calcom:booking block for that +// username+eventType has captchaEnabled=true in its stored content — resolved +// from published content, NEVER from the client-supplied blockId, so a bot +// cannot strip the requirement by omitting or forging the id. Returns false when +// there is no resolver (editor preview / DB-less unit tests), when no published +// block matches (draft-only ⇒ baseline honeypot + rate limit still apply), or on +// a resolver error (logged; a transient DB blip must not block every booking — +// the honeypot + per-IP rate limit remain in force). This is the same source the +// block render (HandleGetForm) reads, keeping "show the widget" and "enforce the +// widget" in lockstep. +func (h *CalcomHandler) bookingRequiresCaptcha(ctx context.Context, username, eventTypeSlug string) bool { + if h.blockConfig == nil { + return false + } + required, err := h.blockConfig.CalcomBookingRequiresCaptcha(ctx, username, eventTypeSlug) + if err != nil { + log.Printf("calcom: resolve captcha requirement for %q/%q: %v", username, eventTypeSlug, err) + return false + } + return required +} + +// HandleCreateBooking creates a booking via Cal.com using CalcomClient. +func (h *CalcomHandler) HandleCreateBooking(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + apiKey, err := h.settings.GetAPIKey(r.Context()) + if err != nil { + h.renderError(w, "Failed to load Cal.com settings", "", false) + return + } + if apiKey == "" { + h.renderError(w, "Cal.com integration is not configured", "", false) + return + } + + if err := r.ParseForm(); err != nil { + h.renderError(w, "Invalid form data", r.FormValue("blockId"), false) + return + } + + blockID := r.FormValue("blockId") + + // Honeypot: real users never fill this hidden field (deliberately + // non-semantic name to dodge Chrome autofill heuristics). Return a fake + // confirmation to bots so they think they succeeded — never reach Cal.com. + if strings.TrimSpace(r.FormValue("bn_message_extra")) != "" { + var buf bytes.Buffer + if err := CalcomConfirmation(BookingResult{Success: true}, blockID).Render(r.Context(), &buf); err != nil { + h.renderError(w, "Failed to render confirmation", blockID, false) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if _, err := w.Write(buf.Bytes()); err != nil { + log.Printf("calcom: write honeypot response: %v", err) + } + return + } + + username := r.FormValue("username") + eventType := r.FormValue("eventType") + startTime := r.FormValue("start") + // Cal.com's bookings endpoint requires `start` in UTC. Slot starts already + // arrive as UTC (…Z); normalize defensively so a future slots-format change + // (e.g. a local UTC offset) can never send a non-UTC start upstream. + if t, err := time.Parse(time.RFC3339, startTime); err == nil { + startTime = t.UTC().Format(time.RFC3339) + } + name := r.FormValue("name") + email := r.FormValue("email") + + // Cheap validation BEFORE the rate-limit allowance is consumed so a flood + // of garbage-email requests can't drain a victim IP's quota (spec § 3.8). + if name == "" || email == "" { + h.renderError(w, "Name and email are required", blockID, false) + return + } + if !emailRE.MatchString(email) { + h.renderError(w, "Invalid email address", blockID, false) + return + } + + ip := helpers.GetRealIP(r) + if h.limiter != nil && !h.limiter.Allow(ip) { + w.WriteHeader(http.StatusTooManyRequests) + h.renderError(w, "Too many bookings from this network. Please try again later.", blockID, false) + return + } + + // Captcha enforcement. Layered ON TOP of the honeypot + per-IP rate limit + // above (verified after both, per the shared captcha design). The + // authoritative source is whether any currently-published calcom:booking + // block for this username+eventType has captchaEnabled=true — resolved from + // published content, never the POST body/blockId — so a bot cannot strip the + // requirement by omitting or forging blockId. When captcha IS required we + // fail CLOSED: a nil shared captcha server (instance secret failed at + // startup) rejects rather than allows. On failure we render the same error + // partial as other validation failures (not a 500), so the visitor can + // re-solve and retry. + if h.bookingRequiresCaptcha(r.Context(), username, eventType) { + if captchaVerifier == nil || !captchaVerifier.VerifyRequest(r) { + h.renderError(w, "Captcha check failed, please try again.", blockID, false) + return + } + } + + loc, tzName := h.eventLocation(r.Context(), apiKey, username, eventType) + + // Resolve the phone region used to convert national numbers to E.164 (Cal.com + // rejects national-format numbers as invalid_number). The visitor's explicit + // country-combobox choice (phoneCountry) wins; otherwise fall back to the + // timezone/locale detection that seeded the combobox default. + region := strings.ToUpper(strings.TrimSpace(r.FormValue("phoneCountry"))) + if _, ok := countryByCode(region); !ok { + region = resolveRegion(r.FormValue("locale"), tzName) + } + payload := routeBookingForm(r.Form, region) + + client := NewCalcomClient(apiKey) + br := BookingRequest{ + EventTypeSlug: eventType, + Username: username, + Start: startTime, + Attendee: BookingAttendee{ + Name: name, + Email: email, + Phone: payload.Phone, + TimeZone: tzName, + }, + BookingFieldsResponses: payload.Responses, + Guests: payload.Guests, + } + booking, err := client.CreateBooking(r.Context(), br) + if err != nil { + // Keep the FULL technical cause server-side (unchanged), but never + // surface it. Classify into curated, user-actionable copy: Tier-1 + // specific messages for visitor-fixable failures (with a retry control + // where re-picking a slot is the fix), else the admin-authored Tier-2 + // message (a hidden form field, defaulted when blank). + log.Printf("calcom: create booking: %v", err) + adminMessage := strings.TrimSpace(r.FormValue("unavailableMessage")) + if adminMessage == "" { + adminMessage = DefaultUnavailableMessage + } + res := classifyBookingError(err, adminMessage) + h.renderError(w, res.message, blockID, res.canRetry) + return + } + + // startTime was normalized to UTC for the Cal.com API above; the + // confirmation must render in the meeting's timezone, not the UTC instant. + formattedDateTime := startTime + if t, err := time.Parse(time.RFC3339, startTime); err == nil { + formattedDateTime = t.In(loc).Format("Monday, January 2, 2006 at 3:04 PM") + } + + result := BookingResult{ + Success: true, + BookingID: booking.Data.UID, + FormattedDateTime: formattedDateTime, + Email: email, + MeetingLink: booking.Data.MeetingLink(), + } + + var buf bytes.Buffer + if err := CalcomConfirmation(result, blockID).Render(r.Context(), &buf); err != nil { + h.renderError(w, "Failed to render confirmation", blockID, false) + return + } + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if _, err := w.Write(buf.Bytes()); err != nil { + log.Printf("failed to write Cal.com confirmation response: %v", err) + } +} + +// HandleCancelBooking cancels a previously-created booking and renders the +// "cancelled" partial (which OOB-restores the date grid so the visitor can +// rebook). It is the server half of the localStorage-backed "manage your +// booking" panel: the widget script populates the hidden `uid` input from the +// visitor's own localStorage and POSTs here. +// +// SECURITY MODEL (deliberate): cancellation is authorized by the booking UID +// alone — the same capability model as Cal.com's own emailed cancel link. The +// UID is an unguessable Cal.com identifier, and the only place it is stored +// client-side is the booking visitor's own localStorage, so a visitor can only +// ever cancel a booking they made. There is no per-visitor auth on the public +// widget to bind it to (bookings are anonymous), so the IP rate limit is the +// abuse guard against someone brute-forcing UIDs. We reuse the booking limiter: +// a cancel is a write against Cal.com just like a booking, so the two share one +// per-IP budget and a flood of cancel attempts can't drain into unlimited +// upstream calls. Cal.com's raw error text is never surfaced — only curated +// copy — and the full cause is logged server-side. +func (h *CalcomHandler) HandleCancelBooking(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + apiKey, err := h.settings.GetAPIKey(r.Context()) + if err != nil { + h.renderError(w, "Failed to load Cal.com settings", "", false) + return + } + if apiKey == "" { + h.renderError(w, "Cal.com integration is not configured", "", false) + return + } + + if err := r.ParseForm(); err != nil { + h.renderError(w, "Invalid form data", r.FormValue("blockId"), false) + return + } + + blockID := r.FormValue("blockId") + uid := strings.TrimSpace(r.FormValue("uid")) + if uid == "" { + h.renderError(w, "Missing booking reference", blockID, false) + return + } + + // Rate-limit by IP (shared booking budget) — the abuse guard for UID-based + // cancellation. Consumed only after the cheap uid presence check so a flood + // of empty requests can't drain a victim IP's quota. + ip := helpers.GetRealIP(r) + if h.limiter != nil && !h.limiter.Allow(ip) { + w.WriteHeader(http.StatusTooManyRequests) + h.renderError(w, "Too many requests from this network. Please try again later.", blockID, false) + return + } + + client := NewCalcomClient(apiKey) + if err := client.CancelBooking(r.Context(), uid, "Cancelled by attendee"); err != nil { + // Full technical cause stays server-side; the visitor sees curated copy. + log.Printf("calcom: cancel booking %q: %v", uid, err) + h.renderError(w, "Sorry, we couldn't cancel that booking. Please try again or use our contact form.", blockID, true) + return + } + + var buf bytes.Buffer + if err := CalcomCancelled(blockID).Render(r.Context(), &buf); err != nil { + h.renderError(w, "Failed to render cancellation", blockID, false) + return + } + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if _, err := w.Write(buf.Bytes()); err != nil { + log.Printf("failed to write Cal.com cancellation response: %v", err) + } +} + +// HandleGetDateGrid returns a freshly rendered date-grid partial for a given +// weekStart + weeks window. Wired to the prev/next month-nav buttons, this +// is the server-side endpoint that drives month navigation; the response +// innerHTML-swaps into #calcom-date-grid-{id}. +func (h *CalcomHandler) HandleGetDateGrid(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + blockID := q.Get("blockId") + username := q.Get("username") + eventType := q.Get("eventType") + + weeks, _ := strconv.Atoi(q.Get("weeks")) + if weeks <= 0 { + weeks = 2 + } + if weeks > 8 { + weeks = 8 // matches the editor's max + } + + // "Today" — and therefore the earliest reachable window — is a date on + // the MEETING's calendar, not the server's. A UTC server clock is a day + // behind a Perth schedule until 08:00 AWST; clamping to it would let the + // grid start on a day the host can no longer be booked. + apiKey, _ := h.settings.GetAPIKey(r.Context()) + loc, _ := h.eventLocation(r.Context(), apiKey, username, eventType) + todayStr := time.Now().In(loc).Format("2006-01-02") + today, _ := time.ParseInLocation("2006-01-02", todayStr, loc) + + start, err := time.ParseInLocation("2006-01-02", q.Get("weekStart"), loc) + if err != nil { + start = today + } + // Visitors cannot navigate before today. + if start.Before(today) { + start = today + } + + config := BookingConfig{ + BlockID: blockID, + Username: username, + EventTypeSlug: eventType, + WeeksToShow: weeks, + WeekStart: start.Format("2006-01-02"), + Today: todayStr, + RangeLabel: formatRangeLabel(start, weeks), + CanGoBack: start.After(today), + Dates: generateDateGridFrom(start, weeks, todayStr), + } + + var buf bytes.Buffer + if err := calcomDateGrid(config).Render(r.Context(), &buf); err != nil { + log.Printf("calcom: render date grid: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if _, err := w.Write(buf.Bytes()); err != nil { + log.Printf("failed to write Cal.com date-grid response: %v", err) + } +} + +// HandleReset rewinds the booking widget to its initial state. The body +// replaces #calcom-slots-{id} with nothing and includes hx-swap-oob clears +// for the form region + a step-indicator rewind — so a visitor pressing +// "Change date" no longer has a stale form lingering from a previous slot. +func (h *CalcomHandler) HandleReset(w http.ResponseWriter, r *http.Request) { + blockID := r.URL.Query().Get("blockId") + var buf bytes.Buffer + if err := CalcomReset(blockID).Render(r.Context(), &buf); err != nil { + log.Printf("calcom: render reset: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if _, err := w.Write(buf.Bytes()); err != nil { + log.Printf("failed to write Cal.com reset response: %v", err) + } +} + +// HandleGetSettings returns the current Cal.com settings: API key (masked) + +// webhook URL + masked webhook secret + last-event status. Secrets are never +// exposed verbatim. +func (h *CalcomHandler) HandleGetSettings(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + apiKey, err := h.settings.GetAPIKey(ctx) + if err != nil { + writeServerError(w, err, "Failed to load settings", "calcomblock.HandleGetSettings") + return + } + secret, _ := h.settings.GetWebhookSecret(ctx) + store, _ := h.settings.GetSettings(ctx) + username, _ := h.settings.GetUsername(ctx) + + // Self-heal: tenants whose API key was saved before this feature shipped + // have no cached username. Fire /me once on the first settings load and + // persist the result so subsequent loads are cache-hits. + if username == "" && apiKey != "" { + client := NewCalcomClient(apiKey) + if me, err := client.GetMe(ctx); err != nil { + log.Printf("calcom: backfill /me in HandleGetSettings: %v", err) + } else { + username = me.Username + if err := h.settings.SetUsername(ctx, me.Username); err != nil { + log.Printf("calcom: persist backfilled username: %v", err) + } + } + } + + resp := map[string]any{ + "api_key_configured": apiKey != "", + "api_key_masked": maskSecret(apiKey), + "username": username, + "webhook_url": h.webhookURL(), + "webhook_secret_configured": secret != "", + "webhook_secret_masked": helpers.MaskSecret(secret), + "last_event_at": store["last_event_at"], + "last_event_type": store["last_event_type"], + } + + writeJSON(w, resp) +} + +// webhookURL builds the externally reachable webhook URL for this tenant. It +// reads the AppURL the CMS injected when the plugin was mounted. +func (h *CalcomHandler) webhookURL() string { + if h.appURL == "" { + return "/api/plugins/calcomblock/webhook" + } + return strings.TrimRight(h.appURL, "/") + "/api/plugins/calcomblock/webhook" +} + +// HandleSaveSettings saves the Cal.com API key +func (h *CalcomHandler) HandleSaveSettings(w http.ResponseWriter, r *http.Request) { + var req struct { + APIKey string `json:"api_key"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + ctx := r.Context() + if err := h.settings.SetAPIKey(ctx, req.APIKey); err != nil { + writeServerError(w, err, "Failed to save settings", "calcomblock.HandleSaveSettings") + return + } + + if req.APIKey == "" { + // Clearing the key also clears the cached identity so a future save + // with a fresh key doesn't reuse the previous owner's username. + if err := h.settings.SetUsername(ctx, ""); err != nil { + log.Printf("calcom: clear username on api-key removal: %v", err) + } + } else { + // Resolve the API key's owner so the block editor doesn't have to + // ask the admin to type their own username. Best-effort: if /me is + // down or rate-limited, the save still succeeds and the admin can + // refresh later via Test Connection. + client := NewCalcomClient(req.APIKey) + if me, err := client.GetMe(ctx); err != nil { + log.Printf("calcom: /me lookup after save: %v", err) + } else if err := h.settings.SetUsername(ctx, me.Username); err != nil { + log.Printf("calcom: persist username after /me: %v", err) + } + + // On the first save with an API key, generate a webhook secret so + // the admin has something to paste into Cal.com's webhook + // configuration immediately. + existing, _ := h.settings.GetWebhookSecret(ctx) + if existing == "" { + secret, err := generateWebhookSecret() + if err != nil { + writeServerError(w, err, "Failed to generate webhook secret", "calcomblock.HandleSaveSettings") + return + } + if err := h.settings.SetWebhookSecret(ctx, secret); err != nil { + writeServerError(w, err, "Failed to save webhook secret", "calcomblock.HandleSaveSettings") + return + } + } + } + + writeJSON(w, map[string]any{ + "success": true, + "api_key_masked": maskSecret(req.APIKey), + "api_key_configured": req.APIKey != "", + }) +} + +// HandleRotateWebhookSecret generates a new webhook signing secret and returns +// its masked form. Invalidates the previous secret immediately — Cal.com will +// reject subsequent retries with the old secret. +func (h *CalcomHandler) HandleRotateWebhookSecret(w http.ResponseWriter, r *http.Request) { + secret, err := generateWebhookSecret() + if err != nil { + writeServerError(w, err, "Failed to generate webhook secret", "calcomblock.HandleRotateWebhookSecret") + return + } + if err := h.settings.SetWebhookSecret(r.Context(), secret); err != nil { + writeServerError(w, err, "Failed to save webhook secret", "calcomblock.HandleRotateWebhookSecret") + return + } + writeJSON(w, map[string]any{ + "success": true, + "webhook_secret_masked": helpers.MaskSecret(secret), + }) +} + +// HandleListEventTypes powers the block editor's event-type dropdown. The +// `username` query param scopes the lookup; an empty value returns the +// API-key owner's own event types. +func (h *CalcomHandler) HandleListEventTypes(w http.ResponseWriter, r *http.Request) { + apiKey, err := h.settings.GetAPIKey(r.Context()) + if err != nil { + writeServerError(w, err, "Failed to read settings", "calcomblock.HandleListEventTypes") + return + } + if apiKey == "" { + writeJSON(w, map[string]any{"success": false, "error": "API key not configured"}) + return + } + username := r.URL.Query().Get("username") + client := NewCalcomClient(apiKey) + ets, err := client.ListEventTypes(r.Context(), username) + if err != nil { + log.Printf("calcom: list event types: %v", err) + writeJSON(w, map[string]any{"success": false, "error": err.Error()}) + return + } + writeJSON(w, map[string]any{"success": true, "event_types": ets}) +} + +// HandleTestConnection verifies the configured API key by asking Cal.com for +// the API-key owner's event types. +func (h *CalcomHandler) HandleTestConnection(w http.ResponseWriter, r *http.Request) { + apiKey, err := h.settings.GetAPIKey(r.Context()) + if err != nil { + writeJSON(w, map[string]any{"success": false, "error": "Failed to load settings"}) + return + } + if apiKey == "" { + writeJSON(w, map[string]any{"success": false, "error": "API key not configured"}) + return + } + + client := NewCalcomClient(apiKey) + ets, err := client.ListEventTypes(r.Context(), "") + if err != nil { + log.Printf("calcom: test connection: %v", err) + writeJSON(w, map[string]any{"success": false, "error": err.Error()}) + return + } + + writeJSON(w, map[string]any{ + "success": true, + "event_types": ets, + }) +} + +// renderError renders an error message +func (h *CalcomHandler) renderError(w http.ResponseWriter, message, blockID string, canRetry bool) { + var buf bytes.Buffer + if err := CalcomError(message, blockID, canRetry).Render(context.Background(), &buf); err != nil { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if _, writeErr := w.Write(htmlErrorFallback(message)); writeErr != nil { + log.Printf("failed to write Cal.com fallback error response: %v", writeErr) + } + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if _, err := w.Write(buf.Bytes()); err != nil { + log.Printf("failed to write Cal.com rendered error response: %v", err) + } +} + +func htmlErrorFallback(message string) []byte { + return []byte(`

` + message + `

`) +} + +// writeJSON sends a JSON response with the standard content type and best-effort logging. +func writeJSON(w http.ResponseWriter, payload any) { + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(payload); err != nil { + log.Printf("calcom: encode JSON response: %v", err) + } +} + +// maskSecret returns a masked version of a secret +func maskSecret(secret string) string { + if len(secret) <= 11 { + return "***" + } + return secret[:7] + "..." + secret[len(secret)-4:] +} diff --git a/handler_captcha_test.go b/handler_captcha_test.go new file mode 100644 index 0000000..1bafbd7 --- /dev/null +++ b/handler_captcha_test.go @@ -0,0 +1,340 @@ +package main + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "strings" + "sync/atomic" + "testing" + + "github.com/google/uuid" + + "git.dev.alexdunmow.com/block/core/captcha" +) + +// fakeBlockResolver is an in-memory blockContentResolver so the booking handler +// can resolve the server-authoritative "does this username+eventType require a +// captcha?" answer without a live database. It mirrors the DB query +// (CalcomBookingRequiresCaptcha), which returns true iff a published +// calcom:booking block for that username+eventTypeSlug has captchaEnabled=true. +// The key is username+eventTypeSlug — deliberately NOT the blockId — so tests +// prove the requirement no longer depends on the client-supplied id. +type fakeBlockResolver struct { + // required maps "username\x00eventTypeSlug" → whether a published block + // requires captcha for that pair. + required map[string]bool + // err, when set, is returned to exercise the resolver-error path. + err error +} + +func (f fakeBlockResolver) CalcomBookingRequiresCaptcha(_ context.Context, username, eventTypeSlug string) (bool, error) { + if f.err != nil { + return false, f.err + } + return f.required[username+"\x00"+eventTypeSlug], nil +} + +// newCalcomCaptchaHandler builds a CalcomHandler whose published calcom:booking +// block for username "alice" / eventType "30min" carries the given +// captchaEnabled flag, plus a persisted API key so the booking flow reaches the +// Cal.com call. The requirement is keyed on username+eventType, NOT a blockId — +// so the booking form may post any blockId (or none) without affecting it. +func newCalcomCaptchaHandler(t *testing.T, captchaEnabled bool) *CalcomHandler { + t.Helper() + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + h.blockConfig = fakeBlockResolver{required: map[string]bool{ + "alice\x0030min": captchaEnabled, + }} + return h +} + +// newCalcomTestCaptchaServer builds a captcha.Server with a low PoW difficulty +// so the challenge solves quickly in tests. +func newCalcomTestCaptchaServer() *captcha.Server { + return captcha.New([]byte("calcom-captcha-test-secret"), captcha.WithChallenge(4, 8, 2)) +} + +// captchaBookingForm builds a booking POST body that passes every cheap +// validation (name+email present, valid email) so the only thing standing +// between the request and the Cal.com call is the captcha check. blockID is +// posted only as a render target (as a real widget would); when empty it is +// omitted entirely, proving the captcha requirement does not depend on it. +func captchaBookingForm(blockID string) url.Values { + v := url.Values{ + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-05-27T10:00:00Z"}, + "name": {"Bob"}, + "email": {"bob@example.com"}, + } + if blockID != "" { + v.Set("blockId", blockID) + } + return v +} + +func postCaptchaBooking(h *CalcomHandler, form url.Values) *httptest.ResponseRecorder { + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("X-Forwarded-For", "198.51.100.7") // TEST-NET-2, isolated bucket + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + return rec +} + +// fakeCalcomServer stands in for Cal.com and counts booking creations, so tests +// can assert whether a submission got past the captcha gate to actually book. +func fakeCalcomServer(t *testing.T) (*httptest.Server, *atomic.Int32) { + t.Helper() + var bookings atomic.Int32 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasPrefix(r.URL.Path, "/bookings") { + bookings.Add(1) + } + _, _ = io.WriteString(w, `{"status":"success","data":{"uid":"u"}}`) + })) + t.Cleanup(srv.Close) + return srv, &bookings +} + +func TestCalcomBooking_CaptchaEnabled_RejectsWithoutToken(t *testing.T) { + fake, bookings := fakeCalcomServer(t) + withCalcomBaseURL(t, fake.URL) + + h := newCalcomCaptchaHandler(t, true) + SetCaptchaServer(newCalcomTestCaptchaServer()) + t.Cleanup(func() { SetCaptchaServer(nil) }) + + rec := postCaptchaBooking(h, captchaBookingForm(uuid.NewString())) // no cap-token + + if got := bookings.Load(); got != 0 { + t.Fatalf("Cal.com booking created %d times; want 0 (missing captcha token must be rejected)", got) + } + if !strings.Contains(rec.Body.String(), "aptcha") { + t.Fatalf("expected captcha error fragment, got: %s", rec.Body.String()) + } +} + +func TestCalcomBooking_CaptchaEnabled_RejectsInvalidToken(t *testing.T) { + fake, bookings := fakeCalcomServer(t) + withCalcomBaseURL(t, fake.URL) + + h := newCalcomCaptchaHandler(t, true) + SetCaptchaServer(newCalcomTestCaptchaServer()) + t.Cleanup(func() { SetCaptchaServer(nil) }) + + form := captchaBookingForm(uuid.NewString()) + form.Set("cap-token", "not-a-real-token") + rec := postCaptchaBooking(h, form) + + if got := bookings.Load(); got != 0 { + t.Fatalf("Cal.com booking created %d times; want 0 (invalid token rejected)", got) + } + if !strings.Contains(rec.Body.String(), "aptcha") { + t.Fatalf("expected captcha error fragment, got: %s", rec.Body.String()) + } +} + +func TestCalcomBooking_CaptchaEnabled_ProceedsWithValidToken(t *testing.T) { + fake, bookings := fakeCalcomServer(t) + withCalcomBaseURL(t, fake.URL) + + srv := newCalcomTestCaptchaServer() + h := newCalcomCaptchaHandler(t, true) + SetCaptchaServer(srv) + t.Cleanup(func() { SetCaptchaServer(nil) }) + + token := mintCalcomCaptchaToken(t, srv) + form := captchaBookingForm(uuid.NewString()) + form.Set("cap-token", token) + rec := postCaptchaBooking(h, form) + + if got := bookings.Load(); got != 1 { + t.Fatalf("Cal.com booking created %d times; want 1 (valid token must pass captcha)", got) + } + if rec.Code != http.StatusOK { + t.Fatalf("status = %d; want 200 (body=%s)", rec.Code, rec.Body.String()) + } + // The captcha token must never be forwarded to Cal.com as a booking field. + if strings.Contains(rec.Body.String(), "cap-token") { + t.Fatalf("cap-token leaked into rendered response: %s", rec.Body.String()) + } +} + +func TestCalcomBooking_CaptchaDisabled_TokenNotRequired(t *testing.T) { + fake, bookings := fakeCalcomServer(t) + withCalcomBaseURL(t, fake.URL) + + h := newCalcomCaptchaHandler(t, false) + // A non-nil server would still reject if enforcement wrongly kicked in; nil + // proves the disabled path never touches the verifier. + SetCaptchaServer(nil) + + rec := postCaptchaBooking(h, captchaBookingForm(uuid.NewString())) // no cap-token + + if got := bookings.Load(); got != 1 { + t.Fatalf("Cal.com booking created %d times; want 1 (captcha disabled)", got) + } + if rec.Code != http.StatusOK { + t.Fatalf("status = %d; want 200 (body=%s)", rec.Code, rec.Body.String()) + } + if strings.Contains(rec.Body.String(), "aptcha") { + t.Fatalf("captcha error fragment rendered when captcha disabled: %s", rec.Body.String()) + } +} + +func TestCalcomBooking_CaptchaEnabled_FailsClosedWhenServerNil(t *testing.T) { + fake, bookings := fakeCalcomServer(t) + withCalcomBaseURL(t, fake.URL) + + h := newCalcomCaptchaHandler(t, true) + // nil captcha server: the instance secret failed at startup. An enabled + // block must reject rather than allow the booking through unverified. + SetCaptchaServer(nil) + + form := captchaBookingForm(uuid.NewString()) + form.Set("cap-token", "anything") + rec := postCaptchaBooking(h, form) + + if got := bookings.Load(); got != 0 { + t.Fatalf("Cal.com booking created %d times; want 0 (fail closed on nil server)", got) + } + if !strings.Contains(rec.Body.String(), "aptcha") { + t.Fatalf("expected captcha error fragment, got: %s", rec.Body.String()) + } +} + +// TestCalcomBooking_CaptchaBypass_BlockIdCannotStripRequirement is the +// regression guard for the security finding (commit 37e88d3b9): the captcha +// requirement derives from published content (username+eventType), NEVER the +// client-supplied blockId. A bot that omits blockId OR forges a random one must +// still be rejected when a published calcom:booking block for that +// username+eventType has captchaEnabled=true. Before the fix, omitting blockId +// caused blockCaptchaEnabled to fail open and book with no captcha. +func TestCalcomBooking_CaptchaBypass_BlockIdCannotStripRequirement(t *testing.T) { + for _, tc := range []struct{ name, blockID string }{ + {"blockId omitted", ""}, + {"blockId forged (random, unresolvable)", uuid.NewString()}, + } { + t.Run(tc.name, func(t *testing.T) { + fake, bookings := fakeCalcomServer(t) + withCalcomBaseURL(t, fake.URL) + + h := newCalcomCaptchaHandler(t, true) // published block requires captcha + SetCaptchaServer(newCalcomTestCaptchaServer()) + t.Cleanup(func() { SetCaptchaServer(nil) }) + + // No valid cap-token; the requirement must not depend on blockId. + rec := postCaptchaBooking(h, captchaBookingForm(tc.blockID)) + + if got := bookings.Load(); got != 0 { + t.Fatalf("Cal.com booking created %d times; want 0 (captcha requirement must not depend on blockId)", got) + } + if !strings.Contains(rec.Body.String(), "aptcha") { + t.Fatalf("expected captcha error fragment, got: %s", rec.Body.String()) + } + }) + } +} + +// TestCalcomBooking_CaptchaRequirement_ResolverErrorFailsOpen documents the +// deliberate choice for the requirement lookup: a resolver (DB) error is logged +// and treated as "not required", so a transient DB blip cannot block every +// booking site-wide. The honeypot + per-IP rate limit remain the baseline +// protection. (This is distinct from the nil-captcha-server path, which fails +// CLOSED when a block IS known to require captcha.) +func TestCalcomBooking_CaptchaRequirement_ResolverErrorFailsOpen(t *testing.T) { + fake, bookings := fakeCalcomServer(t) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + h.blockConfig = fakeBlockResolver{err: errors.New("db unavailable")} + SetCaptchaServer(newCalcomTestCaptchaServer()) + t.Cleanup(func() { SetCaptchaServer(nil) }) + + rec := postCaptchaBooking(h, captchaBookingForm("")) // no cap-token + if got := bookings.Load(); got != 1 { + t.Fatalf("Cal.com booking created %d times; want 1 (resolver error → captcha not enforced; honeypot + rate limit still apply)", got) + } + if rec.Code != http.StatusOK { + t.Fatalf("status = %d; want 200 (body=%s)", rec.Code, rec.Body.String()) + } +} + +// --------------------------------------------------------------------------- +// captcha PoW helpers — local copies of the (unexported) core/captcha PRNG so +// the test can solve a challenge and mint a real verification token. Mirrors +// the helpers in internal/handlers/captcha_challenge_test.go. +// --------------------------------------------------------------------------- + +func mintCalcomCaptchaToken(t *testing.T, srv *captcha.Server) string { + t.Helper() + ch, err := srv.CreateChallenge() + if err != nil { + t.Fatalf("CreateChallenge: %v", err) + } + intSols := solveCalcomChallenge(ch.Token, ch.Challenge.C, ch.Challenge.S, ch.Challenge.D) + sols := make([]string, len(intSols)) + for i, n := range intSols { + sols[i] = strconv.Itoa(n) + } + rd := srv.Redeem(ch.Token, sols) + if !rd.Success || rd.Token == "" { + t.Fatalf("Redeem failed: %+v", rd) + } + return rd.Token +} + +func solveCalcomChallenge(token string, c, s, d int) []int { + sols := make([]int, c) + for i := 1; i <= c; i++ { + salt := calcomPRNG(token+strconv.Itoa(i), s) + target := calcomPRNG(token+strconv.Itoa(i)+"d", d) + for n := 0; ; n++ { + sum := sha256.Sum256([]byte(salt + strconv.Itoa(n))) + h := hex.EncodeToString(sum[:]) + if len(h) >= len(target) && h[:len(target)] == target { + sols[i-1] = n + break + } + } + } + return sols +} + +func calcomPRNG(seed string, length int) string { + const mask = 0xFFFFFFFF + state := calcomFNV1a(seed) + var b strings.Builder + for b.Len() < length { + state ^= (state << 13) & mask + state ^= state >> 17 + state ^= (state << 5) & mask + state &= mask + fmt.Fprintf(&b, "%08x", state) + } + return b.String()[:length] +} + +func calcomFNV1a(s string) uint32 { + var h uint32 = 2166136261 + for _, ch := range s { + h ^= uint32(ch) + h = (h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) & 0xFFFFFFFF + } + return h +} diff --git a/handler_integration_test.go b/handler_integration_test.go new file mode 100644 index 0000000..3ec96de --- /dev/null +++ b/handler_integration_test.go @@ -0,0 +1,1335 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "io" + "log/slog" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "sync" + "sync/atomic" + "testing" + "time" + + "git.dev.alexdunmow.com/block/core/auth" + "github.com/go-chi/chi/v5" + "github.com/google/uuid" +) + +// newTestRouter builds a chi.Router wired exactly like NewCalcomRouter but with +// an in-memory querier and isolated rate limiter — no DB pool required. Tests +// drive HTTP requests through this to exercise the middleware stack +// (requireAdmin in particular). +func newTestRouter(t *testing.T) http.Handler { + t.Helper() + settings, _ := newTestSettings() + limiter := NewRateLimiter(bookingsPerHourPerIP) + h := NewCalcomHandler(settings, limiter, "https://test.localdev.blockninjacms.com") + + r := chi.NewRouter() + r.Get("/slots", h.HandleGetSlots) + r.Get("/form", h.HandleGetForm) + r.Post("/book", h.HandleCreateBooking) + r.Post("/cancel", h.HandleCancelBooking) + r.Get("/reset", h.HandleReset) + r.Get("/date-grid", h.HandleGetDateGrid) + + wh := NewWebhookHandler(settings, slog.Default()) + r.Post("/webhook", wh.Handle) + + r.Group(func(admin chi.Router) { + admin.Use(requireAdmin) + admin.Get("/settings", h.HandleGetSettings) + admin.Post("/settings", h.HandleSaveSettings) + admin.Post("/settings/rotate-webhook-secret", h.HandleRotateWebhookSecret) + admin.Post("/test", h.HandleTestConnection) + admin.Get("/event-types", h.HandleListEventTypes) + }) + return r +} + +// adminCtx returns a context carrying admin-role claims so requireAdmin allows +// the request through. +func adminCtx(ctx context.Context) context.Context { + return auth.WithUser(ctx, &auth.Claims{ + UserID: uuid.New(), + Email: "admin@example.test", + Role: "admin", + }) +} + +// viewerCtx returns a context carrying viewer-role claims — requireAdmin must +// reject this with 403 (not 401 — the user is authenticated, just not +// privileged enough). +func viewerCtx(ctx context.Context) context.Context { + return auth.WithUser(ctx, &auth.Claims{ + UserID: uuid.New(), + Email: "viewer@example.test", + Role: "viewer", + }) +} + +// withCalcomBaseURL overrides the package-level Cal.com base URL for the +// duration of one test, restoring it on test cleanup. This is the seam that +// lets the public handlers (HandleGetSlots, HandleCreateBooking, HandleGetForm, +// HandleTestConnection, HandleListEventTypes) be exercised against an +// httptest.NewServer instead of api.cal.com. +// +// The handlers call NewCalcomClient(apiKey) inline, and NewCalcomClient reads +// calcomBaseURL when it constructs the client struct, so swapping the var +// reroutes every public handler at once with no further injection. +// +// Tests calling this helper must NOT run with t.Parallel — the override is a +// package-level var and would race across parallel goroutines. +func withCalcomBaseURL(t *testing.T, url string) { + t.Helper() + prev := calcomBaseURL + calcomBaseURL = url + // The event-timezone cache is keyed by username|slug, not base URL — wipe + // it on entry and exit so per-test fake servers can't leak zones into + // each other. + resetEventTZCache() + t.Cleanup(func() { + calcomBaseURL = prev + resetEventTZCache() + }) +} + +// --------------------------------------------------------------------------- +// Group A — Integration tests: handler → fake Cal.com via calcomBaseURL swap +// --------------------------------------------------------------------------- + +// TestHandleGetSlots_HappyPath drives HandleGetSlots end-to-end against a fake +// Cal.com server. Asserts the rendered HTML carries the 12-hour formatted slot +// time, AND verifies the outbound request: path, query string, and the +// per-endpoint cal-api-version header. +func TestHandleGetSlots_HappyPath(t *testing.T) { + var ( + gotPath string + gotQuery url.Values + gotHeaders http.Header + ) + // The event's availability schedule is America/Toronto; the /slots + // passthrough captures the outbound request for assertion. + fakeCalcom(t, "America/Toronto", func(w http.ResponseWriter, r *http.Request) { + gotPath = r.URL.Path + gotQuery = r.URL.Query() + gotHeaders = r.Header + // Real cal-api-version 2024-09-04 response shape: each slot carries a + // `start` field (NOT the legacy 2024-08-13 `time` field). Millisecond + // precision mirrors what api.cal.com actually returns on the wire. + _, _ = io.WriteString(w, `{ + "status":"success", + "data":{ + "2026-06-01":[{"start":"2026-06-01T13:00:00.000Z"},{"start":"2026-06-01T15:30:00.000Z"}] + } + }`) + }) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + req := httptest.NewRequest(http.MethodGet, + "/slots?username=alice&eventType=30min&date=2026-06-01&blockId=b1", nil) + rec := httptest.NewRecorder() + h.HandleGetSlots(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + body := rec.Body.String() + // 12-hour formatted times converted into the MEETING's timezone: 13:00Z / + // 15:30Z on June 1 are 9:00 AM / 11:30 AM in America/Toronto (EDT, UTC-4). + if !strings.Contains(body, "9:00 AM") { + t.Errorf("expected '9:00 AM' (13:00Z in America/Toronto) in body, got: %q", body) + } + if !strings.Contains(body, "11:30 AM") { + t.Errorf("expected '11:30 AM' (15:30Z in America/Toronto) in body, got: %q", body) + } + // Regression guard: the widget must NOT render the empty-state when Cal.com + // returned slots. This is the symptom of the 2024-09-04 `start`-vs-`time` + // field mismatch — every slot silently dropped, empty list, "No available + // times" despite a full slate from the API. + if strings.Contains(body, "No available times") { + t.Errorf("rendered empty-state despite Cal.com returning slots (slot-field shape mismatch); body: %q", body) + } + + // Outbound-request assertions. + if gotPath != "/slots" { + t.Errorf("outbound path: got %q, want /slots", gotPath) + } + if got := gotQuery.Get("username"); got != "alice" { + t.Errorf("outbound username: got %q, want alice", got) + } + if got := gotQuery.Get("eventTypeSlug"); got != "30min" { + t.Errorf("outbound eventTypeSlug: got %q, want 30min", got) + } + if got := gotQuery.Get("timeZone"); got != "America/Toronto" { + t.Errorf("outbound timeZone: got %q, want America/Toronto", got) + } + if got := gotHeaders.Get("cal-api-version"); got != versionSlots { + t.Errorf("cal-api-version header: got %q, want %q", got, versionSlots) + } + if got := gotHeaders.Get("Authorization"); got != "Bearer cal_live_test" { + t.Errorf("Authorization header: got %q, want Bearer cal_live_test", got) + } +} + +// TestHandleCreateBooking_HappyPath drives a successful booking end-to-end: +// POST to /book with a valid form → fake Cal.com returns a booking record → +// handler renders the confirmation panel including the meeting/reschedule/cancel +// links. +func TestHandleCreateBooking_HappyPath(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Real 2026-02-25 bookings response: `location` is the canonical meeting + // field; there are NO rescheduleUrl/cancelUrl fields (manage links reach + // the attendee via Cal.com's confirmation email). + _, _ = io.WriteString(w, `{ + "status":"success", + "data":{ + "id":123, + "uid":"booking-uid-123", + "start":"2026-06-01T10:00:00Z", + "end":"2026-06-01T10:30:00Z", + "location":"https://meet.example.com/booking-uid-123" + } + }`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-06-01T10:00:00Z"}, + "name": {"Bob"}, + "email": {"bob@example.com"}, + "phone": {"+1-555-1212"}, + "timezone": {"America/Toronto"}, + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + body := rec.Body.String() + if !strings.Contains(body, "Booking Confirmed!") { + t.Errorf("expected 'Booking Confirmed!' in confirmation HTML, got: %q", body) + } + // Meeting link is derived from the booking's `location` field (2026-02-25), + // NOT the deprecated `meetingUrl`. + if !strings.Contains(body, "https://meet.example.com/booking-uid-123") { + t.Errorf("expected meeting link (from `location`) in body, got: %q", body) + } + if !strings.Contains(body, "bob@example.com") { + t.Errorf("expected attendee email in confirmation, got: %q", body) + } + // Regression guard (F1): the API returns no rescheduleUrl/cancelUrl, so the + // panel must not render those management links. + if strings.Contains(body, "Reschedule") { + t.Errorf("confirmation must not render a Reschedule link (API returns no rescheduleUrl); body: %q", body) + } +} + +// TestHandleCreateBooking_NormalizesStartToUTC guards F4: Cal.com's bookings +// endpoint requires `start` in UTC. A slot start submitted with a non-UTC +// offset must be converted to UTC before it is sent upstream. +func TestHandleCreateBooking_NormalizesStartToUTC(t *testing.T) { + var gotBody string + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + b, _ := io.ReadAll(r.Body) + gotBody = string(b) + _, _ = io.WriteString(w, `{"status":"success","data":{"uid":"u","location":"https://meet.example.com/u"}}`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-05-27T11:00:00+02:00"}, // 11:00 GMT+2 (Rome) == 09:00 UTC + "name": {"Bob"}, + "email": {"bob@example.com"}, + "timezone": {"Europe/Rome"}, + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + if !strings.Contains(gotBody, `"start":"2026-05-27T09:00:00Z"`) { + t.Errorf("outbound `start` not normalized to UTC; got body: %s", gotBody) + } +} + +// TestHandleCreateBooking_HoneypotMakesZeroCalcomCalls verifies the spec-mandated +// property that a tripped honeypot ("bn_message_extra" field) short-circuits the request +// — the fake Cal.com server fails the test if it is ever called. +func TestHandleCreateBooking_HoneypotMakesZeroCalcomCalls(t *testing.T) { + var hits int64 + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + atomic.AddInt64(&hits, 1) + t.Errorf("fake Cal.com was hit despite honeypot trip: %s %s", r.Method, r.URL.Path) + _, _ = io.WriteString(w, `{"status":"success","data":{"uid":"should-not-happen"}}`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-06-01T10:00:00Z"}, + "name": {"Bot"}, + "email": {"bot@spam.example.com"}, + "bn_message_extra": {"spam"}, // honeypot tripped + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200 (fake confirmation), got %d", rec.Code) + } + if !strings.Contains(rec.Body.String(), "Booking Confirmed") { + t.Errorf("expected fake confirmation rendered, got: %q", rec.Body.String()) + } + if got := atomic.LoadInt64(&hits); got != 0 { + t.Errorf("fake Cal.com received %d requests; honeypot must make ZERO upstream calls", got) + } +} + +// captureBookingRequest inspects the Cal.com /bookings POST body and stores the +// parsed JSON for assertions. +type capturedBooking struct { + mu sync.Mutex + body map[string]any +} + +func (c *capturedBooking) handler(t *testing.T) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Non-booking traffic — the event-timezone lookup (/schedules, + // /event-types) — is served a Perth schedule and not captured, so + // these tests exercise the same meeting-timezone resolution the + // production flow uses. + if !strings.HasPrefix(r.URL.Path, "/bookings") { + if strings.HasPrefix(r.URL.Path, "/schedules") { + _, _ = io.WriteString(w, `{"status":"success","data":[{"id":9,"name":"Availability","timeZone":"Australia/Perth","isDefault":true}]}`) + return + } + _, _ = io.WriteString(w, `{"status":"success","data":[{"id":1,"slug":"30min","scheduleId":null,"bookingFields":[]}]}`) + return + } + raw, err := io.ReadAll(r.Body) + if err != nil { + t.Errorf("read body: %v", err) + } + c.mu.Lock() + defer c.mu.Unlock() + if err := json.Unmarshal(raw, &c.body); err != nil { + t.Errorf("decode body: %v (raw: %s)", err, string(raw)) + } + _, _ = io.WriteString(w, `{ + "status":"success", + "data":{ + "uid":"u", + "rescheduleUrl":"https://cal.com/reschedule/u", + "cancelUrl":"https://cal.com/booking/u?cancel=true" + } + }`) + } +} + +// TestHandleCreateBooking_CustomFieldsLandInBookingFieldsResponses asserts the +// full payload shape sent to Cal.com: +// - Built-in fields go into attendee.{name,email,phoneNumber,timeZone}. +// - Custom fields (q1, q2) land in bookingFieldsResponses. +// - Built-ins MUST NOT appear in bookingFieldsResponses (the field-stripping +// contract documented in WO-014/045). +// - The honeypot field "bn_message_extra" must not appear anywhere. +func TestHandleCreateBooking_CustomFieldsLandInBookingFieldsResponses(t *testing.T) { + cap := &capturedBooking{} + fake := httptest.NewServer(cap.handler(t)) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-06-01T10:00:00Z"}, + "name": {"Bob"}, + "email": {"bob@example.com"}, + "phone": {"555-1212"}, + // Stale cached pages still post a visitor timezone; it must not + // reach Cal.com — the attendee carries the meeting's timezone. + "timezone": {"America/Toronto"}, + "q1": {"yes"}, + "q2": {"blue"}, + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + + cap.mu.Lock() + defer cap.mu.Unlock() + if cap.body == nil { + t.Fatalf("Cal.com received no parseable body") + } + + att, ok := cap.body["attendee"].(map[string]any) + if !ok { + t.Fatalf("attendee missing/wrong type in body: %v", cap.body) + } + if att["name"] != "Bob" { + t.Errorf("attendee.name: got %v, want Bob", att["name"]) + } + if att["email"] != "bob@example.com" { + t.Errorf("attendee.email: got %v, want bob@example.com", att["email"]) + } + // Phone is E.164-normalized on the way out: separators stripped. + if att["phoneNumber"] != "5551212" { + t.Errorf("attendee.phoneNumber: got %v, want 5551212 (normalized)", att["phoneNumber"]) + } + if att["timeZone"] != "Australia/Perth" { + t.Errorf("attendee.timeZone: got %v, want Australia/Perth (the meeting's schedule timezone, not the posted visitor field)", att["timeZone"]) + } + + resps, ok := cap.body["bookingFieldsResponses"].(map[string]any) + if !ok { + t.Fatalf("bookingFieldsResponses missing/wrong type in body: %v", cap.body) + } + if resps["q1"] != "yes" { + t.Errorf("bookingFieldsResponses.q1: got %v, want 'yes'", resps["q1"]) + } + if resps["q2"] != "blue" { + t.Errorf("bookingFieldsResponses.q2: got %v, want 'blue'", resps["q2"]) + } + // Built-in / honeypot keys must NEVER appear inside bookingFieldsResponses. + for _, banned := range []string{"name", "email", "phone", "bn_message_extra", "timezone", "blockId", "username", "eventType", "start"} { + if _, present := resps[banned]; present { + t.Errorf("bookingFieldsResponses must not contain built-in key %q (got value %v)", banned, resps[banned]) + } + } +} + +// TestHandleCreateBooking_MultiValueCustomField asserts that a multi-valued +// custom field (e.g. checkboxes with the same name) is sent as a JSON array, +// not the empty/comma-joined string that url.Values.Get would return. +func TestHandleCreateBooking_MultiValueCustomField(t *testing.T) { + cap := &capturedBooking{} + fake := httptest.NewServer(cap.handler(t)) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-06-01T10:00:00Z"}, + "name": {"Bob"}, + "email": {"bob@example.com"}, + "interests": {"ai", "cms"}, // multi-value custom field + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + + cap.mu.Lock() + defer cap.mu.Unlock() + resps, ok := cap.body["bookingFieldsResponses"].(map[string]any) + if !ok { + t.Fatalf("bookingFieldsResponses missing/wrong type: %v", cap.body) + } + got, ok := resps["interests"].([]any) + if !ok { + t.Fatalf("bookingFieldsResponses.interests must be a JSON array, got %T: %v", resps["interests"], resps["interests"]) + } + if len(got) != 2 || got[0] != "ai" || got[1] != "cms" { + t.Errorf("interests array wrong: %v", got) + } +} + +// TestHandleCreateBooking_CalcomReturns4xxRendersCuratedError verifies that when +// Cal.com responds with a 4xx and a structured error message, the handler +// renders CURATED, visitor-facing copy rather than echoing the Cal.com message +// verbatim. The Cal.com message can carry implementation detail (slot IDs, +// internal references) and must not surface to the public visitor. Here the +// message signals a gone slot ("no longer available"), so the handler maps it to +// the specific slot-unavailable Tier-1 copy WITH a retry control. +func TestHandleCreateBooking_CalcomReturns4xxRendersCuratedError(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = io.WriteString(w, `{"message":"Slot no longer available — internal-ref 0xDEADBEEF"}`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-06-01T10:00:00Z"}, + "name": {"Bob"}, + "email": {"bob@example.com"}, + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected 200 with error body, got %d", rec.Code) + } + body := rec.Body.String() + // A gone-slot signal maps to the specific, actionable Tier-1 copy with a + // retry control — never the old generic "Booking failed". + if !strings.Contains(body, "isn") || !strings.Contains(body, "choose another slot") { + t.Errorf("expected curated slot-unavailable message in body, got: %q", body) + } + if !strings.Contains(body, "Try again") { + t.Errorf("expected recoverable slot error to render the retry control, got: %q", body) + } + if strings.Contains(body, "Booking failed") { + t.Errorf("handler still renders the old generic message; body=%q", body) + } + // The Cal.com upstream message — including the internal-ref leak — must + // NEVER reach the visitor. + if strings.Contains(body, "internal-ref") { + t.Errorf("response leaks Cal.com upstream detail 'internal-ref' to visitor; body=%q", body) + } + if strings.Contains(body, "Slot no longer available") { + t.Errorf("response echoes Cal.com message verbatim to visitor; body=%q", body) + } +} + +// TestHandleCreateBooking_TierTwoUsesAdminMessage verifies the admin-authored +// Tier-2 path: an unrecognised / our-side Cal.com failure (here a 5xx) renders +// the per-block `unavailableMessage` from the form, never Cal.com's raw text, +// and WITHOUT a retry control (the visitor can't fix it by re-picking a slot). +func TestHandleCreateBooking_TierTwoUsesAdminMessage(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + _, _ = io.WriteString(w, `{"error":{"code":"INTERNAL_SERVER_ERROR","message":"upstream exploded — trace 0xCAFE"}}`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + const adminMsg = "Please ring the clinic on 1234 to book." + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-06-01T10:00:00Z"}, + "name": {"Bob"}, + "email": {"bob@example.com"}, + "unavailableMessage": {adminMsg}, + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected 200 with error body, got %d", rec.Code) + } + body := rec.Body.String() + if !strings.Contains(body, adminMsg) { + t.Errorf("expected admin-authored Tier-2 message in body, got: %q", body) + } + if strings.Contains(body, "Try again") { + t.Errorf("non-recoverable Tier-2 error must NOT render a retry control; body=%q", body) + } + if strings.Contains(body, "trace") || strings.Contains(body, "upstream exploded") { + t.Errorf("response leaks Cal.com upstream detail to visitor; body=%q", body) + } +} + +// TestHandleCreateBooking_TierTwoDefaultMessage verifies that when the admin +// left `unavailableMessage` blank, an unrecognised failure falls back to +// DefaultUnavailableMessage (not Cal.com's raw text, not the old generic copy). +func TestHandleCreateBooking_TierTwoDefaultMessage(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = io.WriteString(w, `{"error":{"code":"NotFoundException","message":"Event type with slug nope not found"}}`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-06-01T10:00:00Z"}, + "name": {"Bob"}, + "email": {"bob@example.com"}, + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected 200 with error body, got %d", rec.Code) + } + body := rec.Body.String() + if !strings.Contains(body, "use our contact form") { + t.Errorf("expected DefaultUnavailableMessage in body, got: %q", body) + } + if strings.Contains(body, "not found") || strings.Contains(body, "Event type with slug") { + t.Errorf("response leaks Cal.com upstream detail to visitor; body=%q", body) + } +} + +// TestHandleGetForm_FetchesBookingFieldsFromCalcom drives HandleGetForm to fetch +// custom bookingFields from Cal.com (via the GetEventType API) and assert the +// rendered form contains both the textarea and the select with all options. +// Also confirms the per-endpoint cal-api-version header is correct. +func TestHandleGetForm_FetchesBookingFieldsFromCalcom(t *testing.T) { + var gotHeaders http.Header + var gotQuery url.Values + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotHeaders = r.Header + gotQuery = r.URL.Query() + _, _ = io.WriteString(w, `{ + "status":"success", + "data":[{ + "id":7, + "slug":"intake", + "title":"Intake", + "lengthInMinutes":15, + "bookingFields":[ + {"slug":"size","label":"Company size","type":"select","required":true, + "options":[{"label":"1-10","value":"small"},{"label":"11-50","value":"medium"}]}, + {"slug":"notes","label":"Notes","type":"textarea","required":false} + ] + }] + }`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + req := httptest.NewRequest(http.MethodGet, + "/form?blockId=b1&start=2026-06-01T10:00:00Z&date=2026-06-01&username=alice&eventType=intake", + nil) + rec := httptest.NewRecorder() + h.HandleGetForm(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + body := rec.Body.String() + if !strings.Contains(body, "Company size") { + t.Errorf("expected 'Company size' label in form, got: %q", body) + } + if !strings.Contains(body, "Notes") { + t.Errorf("expected 'Notes' label in form, got: %q", body) + } + if !strings.Contains(body, `name="size"`) { + t.Errorf("expected select input name=\"size\" in form, got: %q", body) + } + if !strings.Contains(body, `value="small"`) || !strings.Contains(body, `value="medium"`) { + t.Errorf("expected both select options in form, got: %q", body) + } + if !strings.Contains(body, `name="notes"`) { + t.Errorf("expected textarea name=\"notes\" in form, got: %q", body) + } + + if got := gotHeaders.Get("cal-api-version"); got != versionEventTypes { + t.Errorf("cal-api-version header: got %q, want %q", got, versionEventTypes) + } + if got := gotQuery.Get("username"); got != "alice" { + t.Errorf("outbound username: got %q, want alice", got) + } + if got := gotQuery.Get("eventSlug"); got != "intake" { + t.Errorf("outbound eventSlug: got %q, want intake", got) + } +} + +// TestHandleGetForm_OnFetchErrorFallsBackToDefaults verifies the WO-027 +// fallback: if the Cal.com event-type fetch fails (5xx upstream), HandleGetForm +// still renders the form using the default `name` + `email` fields. +func TestHandleGetForm_OnFetchErrorFallsBackToDefaults(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + _, _ = io.WriteString(w, `{"message":"Cal.com is on fire"}`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + req := httptest.NewRequest(http.MethodGet, + "/form?blockId=b1&start=2026-06-01T10:00:00Z&date=2026-06-01&username=alice&eventType=30min", + nil) + rec := httptest.NewRecorder() + h.HandleGetForm(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200 (fallback form rendered), got %d (body: %s)", rec.Code, rec.Body.String()) + } + body := rec.Body.String() + if !strings.Contains(body, `name="name"`) { + t.Errorf("expected fallback name input in body, got: %q", body) + } + if !strings.Contains(body, `name="email"`) { + t.Errorf("expected fallback email input in body, got: %q", body) + } + if strings.Contains(body, "Cal.com is on fire") { + t.Errorf("response leaks upstream error message to visitor: %q", body) + } +} + +// TestHandleTestConnection_HappyPath drives the admin /test endpoint against a +// fake Cal.com that returns the API-key owner's event types. Asserts the JSON +// success envelope, the rendered event_types list, and the version header. +func TestHandleTestConnection_HappyPath(t *testing.T) { + var gotHeaders http.Header + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotHeaders = r.Header + _, _ = io.WriteString(w, `{ + "status":"success", + "data":[ + {"id":1,"slug":"30min","title":"30 Minute","lengthInMinutes":30}, + {"id":2,"slug":"60min","title":"60 Minute","lengthInMinutes":60} + ] + }`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + req := httptest.NewRequest(http.MethodPost, "/test", nil) + rec := httptest.NewRecorder() + h.HandleTestConnection(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + var resp map[string]any + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode response: %v", err) + } + if success, _ := resp["success"].(bool); !success { + t.Errorf("expected success=true, got: %v", resp) + } + ets, ok := resp["event_types"].([]any) + if !ok { + t.Fatalf("event_types missing or wrong type: %v", resp) + } + if len(ets) != 2 { + t.Errorf("expected 2 event types in response, got %d", len(ets)) + } + if got := gotHeaders.Get("cal-api-version"); got != versionEventTypes { + t.Errorf("cal-api-version header: got %q, want %q", got, versionEventTypes) + } +} + +// TestHandleListEventTypes_HappyPath drives the admin /event-types endpoint +// (the editor dropdown source) against a fake Cal.com. Same shape as +// HandleTestConnection but driven by GET /event-types?username=. +func TestHandleListEventTypes_HappyPath(t *testing.T) { + var gotQuery url.Values + var gotHeaders http.Header + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotQuery = r.URL.Query() + gotHeaders = r.Header + _, _ = io.WriteString(w, `{ + "status":"success", + "data":[ + {"id":7,"slug":"intake","title":"Intake Call","lengthInMinutes":15} + ] + }`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + req := httptest.NewRequest(http.MethodGet, "/event-types?username=alice", nil) + rec := httptest.NewRecorder() + h.HandleListEventTypes(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + var resp map[string]any + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode response: %v", err) + } + if success, _ := resp["success"].(bool); !success { + t.Errorf("expected success=true, got: %v", resp) + } + ets, ok := resp["event_types"].([]any) + if !ok { + t.Fatalf("event_types missing or wrong type: %v", resp) + } + if len(ets) != 1 { + t.Errorf("expected 1 event type in response, got %d", len(ets)) + } + if got := gotQuery.Get("username"); got != "alice" { + t.Errorf("outbound username: got %q, want alice", got) + } + if got := gotHeaders.Get("cal-api-version"); got != versionEventTypes { + t.Errorf("cal-api-version header: got %q, want %q", got, versionEventTypes) + } +} + +// --------------------------------------------------------------------------- +// Auth-hole regression — admin endpoints must 401 without admin context +// --------------------------------------------------------------------------- +// +// The plugin's HTTP routes mount under /api/plugins/calcomblock/* with no +// outer auth wrapper. Admin endpoints (settings, event-types, test, rotate) +// rely on the requireAdmin middleware in handler.go for their entire defence. +// This test drives requests through the full chi.Router via the plugin's +// HTTPHandler entry point and asserts each admin endpoint returns 401 when +// the request carries no admin claims, while public endpoints stay reachable. + +// TestPluginRouter_AdminEndpointsRequireAuth confirms each admin endpoint +// returns 401 Unauthorized when an anonymous request reaches it. Public +// endpoints in the same router remain 200 (or whatever their own logic says). +// +// This is the critical regression guard: the adversarial review caught that +// these endpoints were unprotected before the requireAdmin middleware was +// added. If anyone disables the middleware, this test fails immediately. +func TestPluginRouter_AdminEndpointsRequireAuth(t *testing.T) { + router := newTestRouter(t) + + cases := []struct { + name string + method string + path string + }{ + {"GET /settings", http.MethodGet, "/settings"}, + {"POST /settings", http.MethodPost, "/settings"}, + {"POST /settings/rotate-webhook-secret", http.MethodPost, "/settings/rotate-webhook-secret"}, + {"POST /test", http.MethodPost, "/test"}, + {"GET /event-types", http.MethodGet, "/event-types"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + req := httptest.NewRequest(c.method, c.path, nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + if rec.Code != http.StatusUnauthorized { + t.Errorf("%s without admin claims: expected 401, got %d (body: %q)", c.name, rec.Code, rec.Body.String()) + } + }) + } +} + +// TestPluginRouter_PublicEndpointsStayOpen confirms the public booking routes +// and the HMAC-verified webhook are NOT gated by requireAdmin. +func TestPluginRouter_PublicEndpointsStayOpen(t *testing.T) { + router := newTestRouter(t) + + cases := []struct { + name string + method string + path string + }{ + // Public booking endpoints — should not return 401 (they may return + // other statuses depending on params, but never 401 from requireAdmin). + {"GET /reset", http.MethodGet, "/reset"}, + {"GET /slots", http.MethodGet, "/slots?blockId=b1"}, + {"GET /form", http.MethodGet, "/form?blockId=b1"}, + // /book + /webhook are POST; the auth middleware would intercept GETs + // too, so test those by sending an empty POST and checking != 401. + {"POST /book", http.MethodPost, "/book"}, + {"POST /webhook", http.MethodPost, "/webhook"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + req := httptest.NewRequest(c.method, c.path, nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + if rec.Code == http.StatusUnauthorized { + t.Errorf("%s should not be gated by requireAdmin (got 401)", c.name) + } + }) + } +} + +// TestPluginRouter_AdminEndpointReachableWithAdminClaims verifies a request +// carrying admin claims via auth.WithUser passes the requireAdmin gate. +func TestPluginRouter_AdminEndpointReachableWithAdminClaims(t *testing.T) { + router := newTestRouter(t) + + req := httptest.NewRequest(http.MethodGet, "/settings", nil) + req = req.WithContext(adminCtx(req.Context())) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("admin request to /settings: expected 200, got %d (body: %q)", rec.Code, rec.Body.String()) + } +} + +// TestPluginRouter_AdminEndpointForbiddenForViewer verifies a request carrying +// a viewer-role session is rejected with 403 (not 200, not 401). +func TestPluginRouter_AdminEndpointForbiddenForViewer(t *testing.T) { + router := newTestRouter(t) + + req := httptest.NewRequest(http.MethodGet, "/settings", nil) + req = req.WithContext(viewerCtx(req.Context())) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusForbidden { + t.Errorf("viewer request to /settings: expected 403, got %d (body: %q)", rec.Code, rec.Body.String()) + } +} + +// --------------------------------------------------------------------------- +// Phase F — structural regressions for the visitor-widget polish pass +// --------------------------------------------------------------------------- + +// TestHandleCreateBooking_ConfirmationEmitsOOBClears guards the structural +// fix introduced in Phase A: after a successful POST /book, the response +// body must include hx-swap-oob directives that empty the date grid, slots, +// and form regions plus advance the step indicator to step 4. Without this +// the visitor sees their populated form below the confirmation panel and +// can re-submit (duplicate booking footgun the adversarial review caught). +func TestHandleCreateBooking_ConfirmationEmitsOOBClears(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = io.WriteString(w, `{"status":"success","data":{"uid":"u-conf-1","rescheduleUrl":"https://cal.com/r/u-conf-1","cancelUrl":"https://cal.com/c/u-conf-1"}}`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{ + "blockId": {"b-conf"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-06-01T10:00:00Z"}, + "name": {"Bob"}, + "email": {"bob@example.com"}, + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + body := rec.Body.String() + if !strings.Contains(body, "Booking Confirmed!") { + t.Errorf("expected confirmation in body, got: %q", body) + } + // The body must include OOB-clear directives for the three upstream regions + // plus an OOB step-indicator update marking everything complete. + wantSubstrings := []string{ + `id="calcom-date-grid-b-conf"`, + `id="calcom-slots-b-conf"`, + `id="calcom-form-b-conf"`, + `id="calcom-steps-b-conf"`, + `hx-swap-oob="innerHTML"`, + `hx-swap-oob="true"`, + } + for _, want := range wantSubstrings { + if !strings.Contains(body, want) { + t.Errorf("response missing %q — confirmation must emit hx-swap-oob clears for the upstream regions", want) + } + } +} + +// TestHandleReset_EmitsOOBFormClearAndStepRewind guards the "Change date" +// fix: GET /reset rewinds the step indicator and clears the form region in +// addition to emptying the slots target. +func TestHandleReset_EmitsOOBFormClearAndStepRewind(t *testing.T) { + h, _ := newTestHandler() + req := httptest.NewRequest(http.MethodGet, "/reset?blockId=b-reset", nil) + rec := httptest.NewRecorder() + h.HandleReset(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + body := rec.Body.String() + for _, want := range []string{ + `id="calcom-form-b-reset"`, + `id="calcom-steps-b-reset"`, + `hx-swap-oob="innerHTML"`, + `hx-swap-oob="true"`, + } { + if !strings.Contains(body, want) { + t.Errorf("reset response missing %q", want) + } + } +} + +// TestHandleGetDateGrid_NextWindow exercises Phase C month navigation: GET +// /date-grid with a future weekStart returns a re-rendered grid scoped to +// that window, includes the prev button (CanGoBack=true), and the range +// label reflects the window. +func TestHandleGetDateGrid_NextWindow(t *testing.T) { + router := newTestRouter(t) + + // Pick a weekStart 4 weeks in the future so it's safely after today + // regardless of when the test runs. + future := time.Now().AddDate(0, 0, 28).Format("2006-01-02") + req := httptest.NewRequest(http.MethodGet, + "/date-grid?blockId=b-nav&username=alice&eventType=30min&weeks=2&weekStart="+future, nil) + req = req.WithContext(adminCtx(req.Context())) // route doesn't gate, but consistent + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + body := rec.Body.String() + // Future window must show the prev button (aria-label="Previous dates"). + if !strings.Contains(body, `aria-label="Previous dates"`) { + t.Errorf("future window response missing prev button: %q", body[:min(400, len(body))]) + } + // Next button is always present. + if !strings.Contains(body, `aria-label="Next dates"`) { + t.Errorf("response missing next button: %q", body[:min(400, len(body))]) + } +} + +// TestHandleGetDateGrid_ClampsToToday guards the rule that visitors cannot +// page into the past. A weekStart from 2020 must be clamped to today, and +// the prev button must NOT render (CanGoBack=false). +func TestHandleGetDateGrid_ClampsToToday(t *testing.T) { + router := newTestRouter(t) + req := httptest.NewRequest(http.MethodGet, + "/date-grid?blockId=b-past&username=alice&eventType=30min&weeks=2&weekStart=2020-01-01", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + body := rec.Body.String() + if strings.Contains(body, `aria-label="Previous dates"`) { + t.Errorf("clamped-to-today window must NOT render prev button (CanGoBack=false), body: %q", body[:min(400, len(body))]) + } + if !strings.Contains(body, `aria-label="Next dates"`) { + t.Errorf("response missing next button: %q", body[:min(400, len(body))]) + } +} + +// TestGenerateDateGridFrom_PastDatesFlagged verifies block.go's IsPast +// computation: a window starting 2 days ago flags the first cell as past, +// today as not past. +func TestGenerateDateGridFrom_PastDatesFlagged(t *testing.T) { + start := time.Now().AddDate(0, 0, -2) + dates := generateDateGridFrom(start, 1, time.Now().Format("2006-01-02")) + if len(dates) != 7 { + t.Fatalf("expected 7 cells, got %d", len(dates)) + } + if !dates[0].IsPast { + t.Errorf("first cell (2 days ago) should be IsPast=true: %+v", dates[0]) + } + if !dates[1].IsPast { + t.Errorf("second cell (yesterday) should be IsPast=true: %+v", dates[1]) + } + if dates[2].IsPast { + t.Errorf("third cell (today) should be IsPast=false: %+v", dates[2]) + } + if !dates[2].IsToday { + t.Errorf("third cell should be IsToday=true: %+v", dates[2]) + } +} + +// TestCalcomBookingWidget_RendersAccessibilityScaffolding is the Phase F a11y +// regression: the widget root must carry aria-live="polite" on the three swap +// regions plus role="grid" on the date picker. If any of these regress the +// test fails and the safety net is intact. +func TestCalcomBookingWidget_RendersAccessibilityScaffolding(t *testing.T) { + router := newTestRouter(t) + // Reach the date grid via /date-grid (which renders calcomDateGrid alone) + // and confirm role="grid" is present. + req := httptest.NewRequest(http.MethodGet, + "/date-grid?blockId=b-a11y&username=alice&eventType=30min&weeks=1&weekStart="+time.Now().Format("2006-01-02"), nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + if !strings.Contains(rec.Body.String(), `role="grid"`) { + t.Errorf(`date-grid partial missing role="grid"`) + } + if !strings.Contains(rec.Body.String(), `aria-label="Booking date picker"`) { + t.Errorf(`date-grid partial missing aria-label="Booking date picker"`) + } +} + +// --------------------------------------------------------------------------- +// HandleCancelBooking — "manage your booking" cancel endpoint +// --------------------------------------------------------------------------- + +// TestHandleCancelBooking_HappyPath drives a successful cancel end-to-end: +// POST /cancel with a uid → fake Cal.com accepts the cancel → handler renders +// the "Booking cancelled" partial. Also asserts the outbound request hit the +// confirmed v2 cancel path with the bookings cal-api-version. +func TestHandleCancelBooking_HappyPath(t *testing.T) { + var gotPath, gotVersion string + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotPath = r.URL.Path + gotVersion = r.Header.Get("cal-api-version") + _, _ = io.WriteString(w, `{"status":"success","data":{"id":1,"uid":"booking-uid-123","status":"cancelled"}}`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{ + "blockId": {"b1"}, + "uid": {"booking-uid-123"}, + } + req := httptest.NewRequest(http.MethodPost, "/cancel", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCancelBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + body := rec.Body.String() + if !strings.Contains(body, "Booking cancelled") { + t.Errorf("expected 'Booking cancelled' in cancel HTML, got: %q", body) + } + // OOB rewind of the step indicator to step 1 so the rebook flow starts fresh. + if !strings.Contains(body, `id="calcom-steps-b1"`) || !strings.Contains(body, `hx-swap-oob="true"`) { + t.Errorf("expected OOB step-indicator rewind in cancel body, got: %q", body) + } + if gotPath != "/bookings/booking-uid-123/cancel" { + t.Errorf("upstream cancel path: got %q, want /bookings/booking-uid-123/cancel", gotPath) + } + if gotVersion != versionBookings { + t.Errorf("upstream cal-api-version: got %q, want %q", gotVersion, versionBookings) + } +} + +// TestHandleCancelBooking_MissingUIDMakesZeroCalcomCalls verifies that a cancel +// request with no uid renders an error and never reaches Cal.com (the fake fails +// the test if it is ever called). +func TestHandleCancelBooking_MissingUIDMakesZeroCalcomCalls(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Errorf("Cal.com must NOT be called when uid is missing; got %s %s", r.Method, r.URL.Path) + w.WriteHeader(http.StatusOK) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{"blockId": {"b1"}} // no uid + req := httptest.NewRequest(http.MethodPost, "/cancel", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCancelBooking(rec, req) + + body := rec.Body.String() + if !strings.Contains(body, "Missing booking reference") { + t.Errorf("expected 'Missing booking reference' error, got: %q", body) + } + if strings.Contains(body, "Booking cancelled") { + t.Errorf("must not render success on a missing uid; body=%q", body) + } +} + +// TestHandleCancelBooking_CalcomErrorRendersCuratedRetry verifies that when +// Cal.com rejects the cancel (e.g. 404), the handler renders curated copy WITH a +// retry control and never echoes Cal.com's raw message to the visitor. +func TestHandleCancelBooking_CalcomErrorRendersCuratedRetry(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = io.WriteString(w, `{"error":{"code":"NotFoundException","message":"Booking not found — ref 0xBADF00D"}}`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{"blockId": {"b1"}, "uid": {"gone-uid"}} + req := httptest.NewRequest(http.MethodPost, "/cancel", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCancelBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected 200 with error body, got %d", rec.Code) + } + body := rec.Body.String() + // templ HTML-escapes the apostrophe in "couldn't", so match the parts that + // survive escaping rather than the raw apostrophe. + if !strings.Contains(body, "cancel that booking") { + t.Errorf("expected curated cancel-failure copy, got: %q", body) + } + if !strings.Contains(body, "Try again") { + t.Errorf("expected a retry control on a failed cancel, got: %q", body) + } + if strings.Contains(body, "0xBADF00D") || strings.Contains(body, "Booking not found") { + t.Errorf("response leaks Cal.com upstream detail to visitor; body=%q", body) + } +} + +// TestHandleCancelBooking_RateLimited verifies the IP rate limit guards the +// cancel endpoint (the abuse guard for UID-based cancellation). A limiter with a +// budget of 1 lets the first cancel through and 429s the second from the same IP. +func TestHandleCancelBooking_RateLimited(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = io.WriteString(w, `{"status":"success","data":{"status":"cancelled"}}`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + settings, _ := newTestSettings() + if err := settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + h := NewCalcomHandler(settings, NewRateLimiter(1), "https://test.localdev.blockninjacms.com") + + doCancel := func() *httptest.ResponseRecorder { + form := url.Values{"blockId": {"b1"}, "uid": {"some-uid"}} + req := httptest.NewRequest(http.MethodPost, "/cancel", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.RemoteAddr = "203.0.113.7:5555" // same IP both calls + rec := httptest.NewRecorder() + h.HandleCancelBooking(rec, req) + return rec + } + + if rec := doCancel(); rec.Code != http.StatusOK { + t.Fatalf("first cancel: expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + rec := doCancel() + if rec.Code != http.StatusTooManyRequests { + t.Errorf("second cancel from same IP: expected 429, got %d", rec.Code) + } +} + +// TestCalcomBookingWidget_RendersSavedBookingPanelAndPersistMarkup verifies the +// "manage your booking" scaffolding is present in the server-rendered widget: +// the normally-hidden saved-booking panel (with its cancel form posting /cancel +// and the redo button) and the stable-key derivation in the widget script. +func TestCalcomBookingWidget_RendersSavedBookingPanelAndPersistMarkup(t *testing.T) { + cfg := BookingConfig{ + BlockID: "b-saved", + Username: "alice", + EventTypeSlug: "30min", + WeeksToShow: 1, + WeekStart: time.Now().Format("2006-01-02"), + Today: time.Now().Format("2006-01-02"), + TimeZone: "UTC", + Dates: generateDateGridFrom(time.Now(), 1, time.Now().Format("2006-01-02")), + } + var buf bytes.Buffer + if err := CalcomBookingWidget(cfg).Render(context.Background(), &buf); err != nil { + t.Fatalf("render widget: %v", err) + } + html := buf.String() + for _, want := range []string{ + `id="calcom-saved-b-saved"`, // saved-booking panel container + `data-calcom-saved`, // panel marker the script toggles + `hx-post="/api/plugins/calcomblock/cancel"`, // cancel form posts the new route + `name="uid"`, // hidden uid the script fills from localStorage + `data-calcom-redo`, // reschedule/redo button + `calcom:booking:`, // stable localStorage key prefix in the script + } { + if !strings.Contains(html, want) { + t.Errorf("widget missing %q", want) + } + } + // The saved panel must start hidden (revealed client-side only when storage holds a booking). + if !strings.Contains(html, `calcom-saved-booking hidden`) { + t.Errorf("saved-booking panel must render hidden by default; html=%q", html[:min(len(html), 400)]) + } +} diff --git a/handler_test.go b/handler_test.go new file mode 100644 index 0000000..cb346e2 --- /dev/null +++ b/handler_test.go @@ -0,0 +1,929 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "sync" + "sync/atomic" + "testing" + + "git.dev.alexdunmow.com/block/calcomblock/internal/db" + "git.dev.alexdunmow.com/block/calcomblock/internal/helpers" + "github.com/jackc/pgx/v5/pgconn" +) + +// fakeQuerier is an in-memory implementation of helpers.PluginSettingsQuerier +// used by tests so SettingsManager can persist and read settings without a DB. +type fakeQuerier struct { + mu sync.Mutex + data map[string][]byte +} + +func newFakeQuerier() *fakeQuerier { + return &fakeQuerier{data: map[string][]byte{}} +} + +func (f *fakeQuerier) GetSetting(_ context.Context, key string) (db.Setting, error) { + f.mu.Lock() + defer f.mu.Unlock() + v, ok := f.data[key] + if !ok { + return db.Setting{}, &pgconn.PgError{Code: "P0002", Message: "no rows"} + } + return db.Setting{Key: key, Value: v}, nil +} + +func (f *fakeQuerier) UpsertSetting(_ context.Context, arg db.UpsertSettingParams) (db.Setting, error) { + f.mu.Lock() + defer f.mu.Unlock() + f.data[arg.Key] = arg.Value + return db.Setting(arg), nil +} + +// newTestSettings builds a SettingsManager backed by an in-memory querier and +// a real crypto with a deterministic key — suitable for handler tests. +func newTestSettings() (*SettingsManager, *fakeQuerier) { + q := newFakeQuerier() + c := helpers.NewPluginCrypto("test-secret-key-for-unit-test!!") + return NewSettingsManager(c, q), q +} + +// newTestHandler builds a CalcomHandler wired with an isolated rate limiter +// — handy for tests that need to drive HandleCreateBooking. +func newTestHandler() (*CalcomHandler, *fakeQuerier) { + s, q := newTestSettings() + return NewCalcomHandler(s, NewRateLimiter(bookingsPerHourPerIP), "https://test.localdev.blockninjacms.com"), q +} + +// TestWriteServerError_DoesNotLeakInternalError verifies that writeServerError +// returns the public message to the HTTP client and does NOT expose the raw +// internal error string. This is the canonical behaviour required for the +// HandleSaveSettings error branch. +func TestWriteServerError_DoesNotLeakInternalError(t *testing.T) { + internalErrMsg := "secret connection string dsn=postgres://user:pass@db/internal" + internalErr := errors.New(internalErrMsg) + + rec := httptest.NewRecorder() + writeServerError(rec, internalErr, "Failed to save settings", "calcomblock.HandleSaveSettings") + + if rec.Code != http.StatusInternalServerError { + t.Errorf("expected status 500, got %d", rec.Code) + } + + responseBody := rec.Body.String() + + if !strings.Contains(responseBody, "Failed to save settings") { + t.Errorf("expected response to contain \"Failed to save settings\", got: %q", responseBody) + } + + if strings.Contains(responseBody, internalErrMsg) { + t.Errorf("response leaks internal error detail: %q", responseBody) + } + + for _, sensitiveFragment := range []string{"postgres://", "user:pass", "dsn=", "internal"} { + if strings.Contains(responseBody, sensitiveFragment) { + t.Errorf("response leaks sensitive fragment %q in body: %q", sensitiveFragment, responseBody) + } + } +} + +// TestWriteInternalServerError_ReturnsGenericMessage verifies that +// writeInternalServerError returns "Internal Server Error" (not the raw err). +func TestWriteInternalServerError_ReturnsGenericMessage(t *testing.T) { + internalErr := errors.New("secret internal detail: file=/etc/secret key=abc123") + + rec := httptest.NewRecorder() + writeInternalServerError(rec, internalErr, "calcomblock.SomeHandler") + + if rec.Code != http.StatusInternalServerError { + t.Errorf("expected status 500, got %d", rec.Code) + } + + responseBody := rec.Body.String() + + if !strings.Contains(responseBody, "Internal Server Error") { + t.Errorf("expected \"Internal Server Error\" in body, got: %q", responseBody) + } + + if strings.Contains(responseBody, "secret internal detail") { + t.Errorf("response leaks internal error: %q", responseBody) + } +} + +// TestHandleSaveSettings_SuccessPath verifies the success branch works correctly. +func TestHandleSaveSettings_SuccessPath(t *testing.T) { + h, _ := newTestHandler() + + body, _ := json.Marshal(map[string]string{"api_key": "cal_live_abc123"}) + req := httptest.NewRequest(http.MethodPost, "/settings", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + + h.HandleSaveSettings(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected status 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + + var resp map[string]any + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("failed to decode response: %v", err) + } + if success, _ := resp["success"].(bool); !success { + t.Errorf("expected success=true in response, got: %v", resp) + } +} + +// TestHandleSaveSettings_InvalidBody verifies that a malformed request body +// returns 400 Bad Request. +func TestHandleSaveSettings_InvalidBody(t *testing.T) { + h, _ := newTestHandler() + + req := httptest.NewRequest(http.MethodPost, "/settings", strings.NewReader("{bad json}")) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + + h.HandleSaveSettings(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Errorf("expected status 400, got %d", rec.Code) + } +} + +// TestSettingsManager_APIKeyPersistsAcrossInstances ensures a new SettingsManager +// backed by the same querier still sees a previously stored API key — verifying +// the rewrite genuinely persists rather than holding the value in memory. +func TestSettingsManager_APIKeyPersistsAcrossInstances(t *testing.T) { + q := newFakeQuerier() + c := helpers.NewPluginCrypto("test-secret-key-for-unit-test!!") + a := NewSettingsManager(c, q) + + if err := a.SetAPIKey(context.Background(), "cal_live_persisted"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + // Build a fresh manager over the same querier — simulates server restart. + b := NewSettingsManager(c, q) + got, err := b.GetAPIKey(context.Background()) + if err != nil { + t.Fatalf("GetAPIKey: %v", err) + } + if got != "cal_live_persisted" { + t.Errorf("expected persisted key 'cal_live_persisted', got %q", got) + } +} + +// --------------------------------------------------------------------------- +// WO-043 — HandleGetSlots error paths (no Cal.com call needed for these paths) +// --------------------------------------------------------------------------- + +// TestHandleGetSlots_MissingParams verifies that a GET request missing required +// params (username, eventType, date) causes HandleGetSlots to render an HTML +// error rather than attempt any Cal.com request. The apiKey must be set so the +// handler reaches the param-check logic. +func TestHandleGetSlots_MissingParams(t *testing.T) { + h, q := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + _ = q + + // Missing username, eventType, and date. + req := httptest.NewRequest(http.MethodGet, "/slots?blockId=b1", nil) + rec := httptest.NewRecorder() + h.HandleGetSlots(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected 200 (error rendered in body), got %d", rec.Code) + } + body := rec.Body.String() + if !strings.Contains(body, "Missing required parameters") { + t.Errorf("expected 'Missing required parameters' in body, got: %q", body) + } +} + +// TestHandleGetSlots_NoAPIKey verifies that when no API key is configured the +// handler renders a "not configured" error without making any Cal.com request. +func TestHandleGetSlots_NoAPIKey(t *testing.T) { + h, _ := newTestHandler() + // No apiKey stored — leave settings empty. + + req := httptest.NewRequest(http.MethodGet, "/slots?username=alice&eventType=30min&date=2026-05-27&blockId=b1", nil) + rec := httptest.NewRecorder() + h.HandleGetSlots(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected 200 (error rendered in body), got %d", rec.Code) + } + body := rec.Body.String() + if !strings.Contains(body, "not configured") { + t.Errorf("expected 'not configured' in body, got: %q", body) + } +} + +// TestHandleGetSlots_InvalidDate verifies that a date param that doesn't match +// YYYY-MM-DD produces a "Invalid date format" error rendered in HTML. +func TestHandleGetSlots_InvalidDate(t *testing.T) { + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + req := httptest.NewRequest(http.MethodGet, "/slots?username=alice&eventType=30min&date=not-a-date&blockId=b1", nil) + rec := httptest.NewRecorder() + h.HandleGetSlots(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected 200 (error rendered in body), got %d", rec.Code) + } + body := rec.Body.String() + if !strings.Contains(body, "Invalid date format") { + t.Errorf("expected 'Invalid date format' in body, got: %q", body) + } +} + +// TestHandleGetSlots_HappyPath — TODO: requires a live-or-mocked Cal.com +// endpoint because the handler constructs its own client internally via +// NewCalcomClient(apiKey). Injecting a fake transport at handler level is out +// of scope for WO-043. The client-level happy-path coverage exists in +// client_test.go (TestGetSlots_URLAndHeaders, TestGetSlots_ParsesResponse). + +// TestHandleGetSlots_TimezonePassthrough — TODO: same constraint as HappyPath. +// Timezone propagation is covered at the client layer in +// TestGetSlots_URLAndHeaders which verifies the timeZone query param. + +// --------------------------------------------------------------------------- +// WO-044 — Anti-abuse: honeypot, invalid email, rate limit +// --------------------------------------------------------------------------- + +// TestHandleCreateBooking_HoneypotShortCircuits verifies that a POST with the +// honeypot field ("bn_message_extra") populated never reaches Cal.com (hitCount stays 0) +// and instead returns a fake 200 confirmation to fool the bot. +func TestHandleCreateBooking_HoneypotShortCircuits(t *testing.T) { + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-05-27T10:00:00Z"}, + "name": {"Bob"}, + "email": {"bob@example.com"}, + "bn_message_extra": {"spam"}, // honeypot tripped + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected 200 (bot thinks it succeeded), got %d", rec.Code) + } + // The honeypot path renders CalcomConfirmation — it contains "Booking Confirmed!" + body := rec.Body.String() + if !strings.Contains(body, "Booking Confirmed") { + t.Errorf("expected honeypot response to contain 'Booking Confirmed', got: %q", body) + } +} + +// TestHandleCreateBooking_InvalidEmail verifies that an email address failing +// the regex check returns a rendered HTML error with "Invalid email" text. +// The email check happens BEFORE rate-limit consumption (spec § 3.8), so a +// flood of bad-email requests cannot drain a victim IP's quota. +func TestHandleCreateBooking_InvalidEmail(t *testing.T) { + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + for _, badEmail := range []string{"", "not-an-email", "@example.com", "x@"} { + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-05-27T10:00:00Z"}, + "name": {"Bob"}, + "email": {badEmail}, + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("email=%q: expected 200 with error body, got %d", badEmail, rec.Code) + } + body := rec.Body.String() + // Empty email triggers "Name and email are required"; others trigger "Invalid email address" + if !strings.Contains(body, "email") && !strings.Contains(body, "Email") { + t.Errorf("email=%q: expected email-related error in body, got: %q", badEmail, body) + } + } +} + +// validBookingFormRequest builds a POST request whose form passes every cheap +// validation (name+email present, email regex). The Cal.com call therefore +// fires, so callers must point calcomBaseURL at a fake server via +// withCalcomBaseURL. +func validBookingFormRequest(t *testing.T, ip string) *http.Request { + t.Helper() + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-05-27T10:00:00Z"}, + "name": {"Bob"}, + "email": {"bob@example.com"}, + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + if ip != "" { + req.Header.Set("X-Forwarded-For", ip) + } + return req +} + +// TestHandleCreateBooking_BadEmailDoesNotConsumeRateLimit guards the spec § 3.8 +// requirement that the email regex runs BEFORE rate-limit consumption. Without +// this ordering, a single malicious actor could drain an honest user's IP +// quota by spraying invalid-email requests. +func TestHandleCreateBooking_BadEmailDoesNotConsumeRateLimit(t *testing.T) { + // Fake Cal.com — counts booking creations. Garbage-email requests must + // never reach this server; only the final honest request may. (The honest + // request also triggers the event-timezone lookup — /schedules — which is + // not a booking and not counted.) + var calcomHits atomic.Int32 + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasPrefix(r.URL.Path, "/bookings") { + calcomHits.Add(1) + } + _, _ = io.WriteString(w, `{"status":"success","data":{"uid":"u"}}`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + const ip = "203.0.113.99" + + // bookingsPerHourPerIP+1 garbage-email requests — each rejected at the + // email-regex check, no rate-limit slots consumed, no Cal.com calls. + for i := range bookingsPerHourPerIP + 1 { + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-05-27T10:00:00Z"}, + "name": {"Bot"}, + "email": {"not-an-email"}, + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("X-Forwarded-For", ip) + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + if rec.Code == http.StatusTooManyRequests { + t.Fatalf("garbage-email attempt %d was rate-limited — email regex must run before rate-limit per spec § 3.8", i+1) + } + } + if hits := calcomHits.Load(); hits != 0 { + t.Fatalf("garbage-email phase hit Cal.com %d times — should be 0", hits) + } + + // Final honest request — passes regex, consumes first rate-limit slot, + // reaches Cal.com. Asserts the bucket was untouched by the garbage flood. + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, validBookingFormRequest(t, ip)) + if rec.Code == http.StatusTooManyRequests { + t.Errorf("honest request was rate-limited because garbage-email requests burned the bucket (spec § 3.8 violation)") + } + if hits := calcomHits.Load(); hits != 1 { + t.Errorf("expected exactly 1 Cal.com hit (the honest request), got %d", hits) + } +} + +// TestHandleCreateBooking_RateLimitAfter10 confirms the per-IP cap: 10 valid +// requests from the same IP pass, the 11th gets 429. The fake Cal.com is +// configured to return success so every legitimate request consumes one slot. +func TestHandleCreateBooking_RateLimitAfter10(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = io.WriteString(w, `{"status":"success","data":{"uid":"u","rescheduleUrl":"r","cancelUrl":"c"}}`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + const testIP = "203.0.113.42" // TEST-NET-3, safe for tests + + for i := range bookingsPerHourPerIP { + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, validBookingFormRequest(t, testIP)) + if rec.Code == http.StatusTooManyRequests { + t.Fatalf("attempt %d was rate-limited too early (expected first %d to be allowed)", i+1, bookingsPerHourPerIP) + } + } + + // 11th request — must be rate-limited. + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, validBookingFormRequest(t, testIP)) + if rec.Code != http.StatusTooManyRequests { + t.Errorf("11th attempt: expected 429, got %d (body: %q)", rec.Code, rec.Body.String()) + } +} + +// --------------------------------------------------------------------------- +// WO-045 — routeBookingForm unit tests (field-routing correctness) +// --------------------------------------------------------------------------- + +// TestRouteBookingForm_BuiltinExcluded verifies control fields and the attendee +// phone are kept out of bookingFieldsResponses, the phone routes to +// attendee.phoneNumber (E.164-normalized), and only genuinely custom keys +// survive as responses. +func TestRouteBookingForm_BuiltinExcluded(t *testing.T) { + form := map[string][]string{ + // Control/built-ins that must be excluded from responses: + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-05-27T10:00:00Z"}, + "timezone": {"America/Toronto"}, + "bn_message_extra": {""}, // honeypot (empty) + "name": {"Bob"}, + "email": {"bob@example.com"}, + "phone": {"+1-555-1234"}, + // Custom fields that MUST survive: + "q1": {"yes"}, + "custom_field": {"custom_value"}, + } + + got := routeBookingForm(form, "") + if got.Responses == nil { + t.Fatal("expected non-nil responses when custom fields are present") + } + for _, builtin := range []string{"blockId", "username", "eventType", "start", "timezone", "bn_message_extra", "name", "email", "phone"} { + if _, present := got.Responses[builtin]; present { + t.Errorf("built-in field %q must not appear in bookingFieldsResponses", builtin) + } + } + // Phone routes to the attendee, separators stripped. + if got.Phone != "+15551234" { + t.Errorf("expected attendee phone '+15551234', got: %q", got.Phone) + } + if got.Responses["q1"] != "yes" { + t.Errorf("expected q1='yes', got: %v", got.Responses["q1"]) + } + if got.Responses["custom_field"] != "custom_value" { + t.Errorf("expected custom_field='custom_value', got: %v", got.Responses["custom_field"]) + } +} + +// TestRouteBookingForm_MultiValue verifies that form values with multiple +// entries (multi-select checkboxes) are preserved as a JSON array rather than +// collapsed to a single string. +func TestRouteBookingForm_MultiValue(t *testing.T) { + form := map[string][]string{ + "topics": {"go", "rust", "zig"}, // multiple selections + } + + got := routeBookingForm(form, "") + if got.Responses == nil { + t.Fatal("expected non-nil responses") + } + vs, ok := got.Responses["topics"].([]any) + if !ok { + t.Fatalf("expected topics to be []any, got %T: %v", got.Responses["topics"], got.Responses["topics"]) + } + if len(vs) != 3 || vs[0] != "go" || vs[1] != "rust" || vs[2] != "zig" { + t.Errorf("multi-value slice wrong: %v", vs) + } +} + +// TestRouteBookingForm_EmptyReturnsNil verifies that a form containing only +// control fields yields nil responses so the omitempty JSON tag drops the field. +func TestRouteBookingForm_EmptyReturnsNil(t *testing.T) { + form := map[string][]string{ + "blockId": {"b1"}, + "username": {"alice"}, + "name": {"Bob"}, + "email": {"bob@example.com"}, + } + + got := routeBookingForm(form, "") + if got.Responses != nil { + t.Errorf("expected nil responses when only control fields present, got: %v", got.Responses) + } +} + +// --------------------------------------------------------------------------- +// WO-046 — HandleGetForm default rendering + admin endpoint shapes +// --------------------------------------------------------------------------- + +// TestHandleGetForm_NoBookingFieldsRendersDefaults verifies that when no API +// key is configured (so the Cal.com event-type fetch is skipped), the form +// falls back to rendering the default name + email fields. +func TestHandleGetForm_NoBookingFieldsRendersDefaults(t *testing.T) { + h, _ := newTestHandler() + // No apiKey set — handler skips GetEventType and uses default fields. + + req := httptest.NewRequest(http.MethodGet, + "/form?blockId=b1&start=2026-05-27T10:00:00Z&date=2026-05-27&username=alice&eventType=30min&timezone=UTC", + nil) + rec := httptest.NewRecorder() + h.HandleGetForm(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected 200, got %d (body: %q)", rec.Code, rec.Body.String()) + } + body := rec.Body.String() + if !strings.Contains(body, `name="name"`) { + t.Errorf("expected name input in default form, body: %q", body) + } + if !strings.Contains(body, `name="email"`) { + t.Errorf("expected email input in default form, body: %q", body) + } +} + +// TestHandleListEventTypes_NotConfigured verifies the JSON error shape when no +// API key has been stored. No outbound request should be made. +func TestHandleListEventTypes_NotConfigured(t *testing.T) { + h, _ := newTestHandler() + // No apiKey — handler returns the "not configured" JSON response. + + req := httptest.NewRequest(http.MethodGet, "/event-types?username=alice", nil) + rec := httptest.NewRecorder() + h.HandleListEventTypes(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected 200, got %d", rec.Code) + } + var got map[string]any + if err := json.NewDecoder(rec.Body).Decode(&got); err != nil { + t.Fatalf("decode response: %v", err) + } + if success, _ := got["success"].(bool); success { + t.Errorf("expected success=false, got: %v", got) + } + if errMsg, _ := got["error"].(string); errMsg != "API key not configured" { + t.Errorf("expected error='API key not configured', got: %q", errMsg) + } +} + +// TestHandleGetSettings_ShapeWhenEmpty verifies the JSON shape when no Cal.com +// settings have been configured: api_key_configured=false, webhook_url present, +// webhook_secret_configured=false. +func TestHandleGetSettings_ShapeWhenEmpty(t *testing.T) { + h, _ := newTestHandler() + + req := httptest.NewRequest(http.MethodGet, "/settings", nil) + rec := httptest.NewRecorder() + h.HandleGetSettings(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected 200, got %d (body: %q)", rec.Code, rec.Body.String()) + } + var got map[string]any + if err := json.NewDecoder(rec.Body).Decode(&got); err != nil { + t.Fatalf("decode response: %v", err) + } + if v, _ := got["api_key_configured"].(bool); v { + t.Errorf("expected api_key_configured=false, got true") + } + if _, present := got["webhook_url"]; !present { + t.Errorf("expected webhook_url field in response, got: %v", got) + } + if v, _ := got["webhook_secret_configured"].(bool); v { + t.Errorf("expected webhook_secret_configured=false, got true") + } +} + +// TestHandleGetSettings_ShapeWhenConfigured verifies that after storing an API +// key and a webhook secret the settings response exposes masked values and sets +// the boolean flags to true. +func TestHandleGetSettings_ShapeWhenConfigured(t *testing.T) { + h, _ := newTestHandler() + ctx := context.Background() + + if err := h.settings.SetAPIKey(ctx, "cal_live_abc123456789"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + if err := h.settings.SetWebhookSecret(ctx, "deadbeefdeadbeefdeadbeefdeadbeef00000000000000000000000000000000"); err != nil { + t.Fatalf("SetWebhookSecret: %v", err) + } + + req := httptest.NewRequest(http.MethodGet, "/settings", nil) + rec := httptest.NewRecorder() + h.HandleGetSettings(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("expected 200, got %d (body: %q)", rec.Code, rec.Body.String()) + } + var got map[string]any + if err := json.NewDecoder(rec.Body).Decode(&got); err != nil { + t.Fatalf("decode response: %v", err) + } + if v, _ := got["api_key_configured"].(bool); !v { + t.Errorf("expected api_key_configured=true, got false; resp=%v", got) + } + if _, present := got["api_key_masked"]; !present { + t.Errorf("expected api_key_masked field in response") + } + if v, _ := got["webhook_secret_configured"].(bool); !v { + t.Errorf("expected webhook_secret_configured=true, got false; resp=%v", got) + } + if _, present := got["webhook_secret_masked"]; !present { + t.Errorf("expected webhook_secret_masked field in response") + } +} + +// TestHandleSaveSettings_PersistsUsernameFromMe asserts that a successful API +// key save fires Cal.com /v2/me and persists the resolved username so the +// block editor doesn't have to ask the admin to type it. +func TestHandleSaveSettings_PersistsUsernameFromMe(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/me" { + t.Errorf("unexpected upstream path %q (want /me)", r.URL.Path) + } + _, _ = io.WriteString(w, `{"status":"success","data":{"username":"alice","email":"alice@example.com"}}`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + body := bytes.NewBufferString(`{"api_key":"cal_live_test"}`) + req := httptest.NewRequest(http.MethodPost, "/settings", body) + rec := httptest.NewRecorder() + h.HandleSaveSettings(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String()) + } + got, err := h.settings.GetUsername(context.Background()) + if err != nil { + t.Fatalf("GetUsername: %v", err) + } + if got != "alice" { + t.Errorf("expected username alice, got %q", got) + } +} + +// TestHandleSaveSettings_MeFailureDoesNotBlockSave guarantees that a transient +// Cal.com /me failure does NOT cause the API key save to fail. The key is +// still persisted; username is left empty for the admin to refresh later. +func TestHandleSaveSettings_MeFailureDoesNotBlockSave(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + body := bytes.NewBufferString(`{"api_key":"cal_live_test"}`) + req := httptest.NewRequest(http.MethodPost, "/settings", body) + rec := httptest.NewRecorder() + h.HandleSaveSettings(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200 even when /me is down, got %d: %s", rec.Code, rec.Body.String()) + } + gotKey, err := h.settings.GetAPIKey(context.Background()) + if err != nil { + t.Fatalf("GetAPIKey: %v", err) + } + if gotKey != "cal_live_test" { + t.Errorf("API key should still be saved, got %q", gotKey) + } + gotUser, _ := h.settings.GetUsername(context.Background()) + if gotUser != "" { + t.Errorf("username should be empty when /me failed, got %q", gotUser) + } +} + +// TestHandleSaveSettings_ClearingKeyClearsUsername confirms that posting an +// empty api_key both clears the key and the cached username so the next save +// re-derives identity from the fresh key. +func TestHandleSaveSettings_ClearingKeyClearsUsername(t *testing.T) { + h, _ := newTestHandler() + ctx := context.Background() + if err := h.settings.SetAPIKey(ctx, "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + if err := h.settings.SetUsername(ctx, "alice"); err != nil { + t.Fatalf("SetUsername: %v", err) + } + + body := bytes.NewBufferString(`{"api_key":""}`) + req := httptest.NewRequest(http.MethodPost, "/settings", body) + rec := httptest.NewRecorder() + h.HandleSaveSettings(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d", rec.Code) + } + got, _ := h.settings.GetUsername(ctx) + if got != "" { + t.Errorf("username should be cleared on key removal, got %q", got) + } +} + +// TestHandleGetSettings_IncludesUsername confirms the JSON response surfaces +// the cached username so the block editor can stamp it into content without +// asking the admin to type it. +func TestHandleGetSettings_IncludesUsername(t *testing.T) { + h, _ := newTestHandler() + ctx := context.Background() + if err := h.settings.SetAPIKey(ctx, "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + if err := h.settings.SetUsername(ctx, "alice"); err != nil { + t.Fatalf("SetUsername: %v", err) + } + + req := httptest.NewRequest(http.MethodGet, "/settings", nil) + rec := httptest.NewRecorder() + h.HandleGetSettings(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d", rec.Code) + } + var got map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil { + t.Fatalf("decode response: %v", err) + } + if got["username"] != "alice" { + t.Errorf("expected username=alice in response, got %v", got["username"]) + } +} + +// TestHandleGetSettings_BackfillsUsernameWhenMissing covers the upgrade path for +// tenants whose API key was saved BEFORE the auto-username feature shipped: the +// key is present, username is empty, so HandleGetSettings lazily fires /v2/me +// and persists the result. Without this self-heal step those tenants would have +// to manually clear and re-save the key. +func TestHandleGetSettings_BackfillsUsernameWhenMissing(t *testing.T) { + var meHits atomic.Int32 + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/me" { + t.Errorf("unexpected upstream path %q (want /me)", r.URL.Path) + } + meHits.Add(1) + _, _ = io.WriteString(w, `{"status":"success","data":{"username":"alice","email":"alice@example.com"}}`) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + ctx := context.Background() + if err := h.settings.SetAPIKey(ctx, "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + // Deliberately leave username unset to simulate a pre-feature save. + + req := httptest.NewRequest(http.MethodGet, "/settings", nil) + rec := httptest.NewRecorder() + h.HandleGetSettings(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String()) + } + var got map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil { + t.Fatalf("decode response: %v", err) + } + if got["username"] != "alice" { + t.Errorf("expected username=alice in response, got %v", got["username"]) + } + persisted, _ := h.settings.GetUsername(ctx) + if persisted != "alice" { + t.Errorf("expected backfill to persist username, got %q", persisted) + } + + // A second GET must NOT re-fire /me — the cache is now warm. + rec2 := httptest.NewRecorder() + h.HandleGetSettings(rec2, req) + if hits := meHits.Load(); hits != 1 { + t.Errorf("expected exactly 1 /me hit after both GETs, got %d", hits) + } +} + +// TestHandleGetSettings_NoBackfillWhenNoAPIKey guards against the backfill +// firing /me with an empty key — that would 401 every GET on an unconfigured +// plugin. +func TestHandleGetSettings_NoBackfillWhenNoAPIKey(t *testing.T) { + var meHits atomic.Int32 + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + meHits.Add(1) + w.WriteHeader(http.StatusUnauthorized) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + req := httptest.NewRequest(http.MethodGet, "/settings", nil) + rec := httptest.NewRecorder() + h.HandleGetSettings(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d", rec.Code) + } + if hits := meHits.Load(); hits != 0 { + t.Errorf("expected zero /me hits without an API key, got %d", hits) + } +} + +// TestHandleGetSettings_BackfillFailureIsNonFatal asserts that a /me error +// during backfill never prevents settings from rendering — the response is +// still 200 with username left empty. +func TestHandleGetSettings_BackfillFailureIsNonFatal(t *testing.T) { + fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + })) + t.Cleanup(fake.Close) + withCalcomBaseURL(t, fake.URL) + + h, _ := newTestHandler() + ctx := context.Background() + if err := h.settings.SetAPIKey(ctx, "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + + req := httptest.NewRequest(http.MethodGet, "/settings", nil) + rec := httptest.NewRecorder() + h.HandleGetSettings(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String()) + } + var got map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil { + t.Fatalf("decode response: %v", err) + } + if got["username"] != "" { + t.Errorf("expected empty username on /me failure, got %v", got["username"]) + } +} + +// TestSettingsManager_UsernamePersists round-trips username through the in-memory +// settings store: empty by default, persisted via SetUsername, read back via +// GetUsername. Username is NOT a secret so it is stored as plain text. +func TestSettingsManager_UsernamePersists(t *testing.T) { + s, _ := newTestSettings() + ctx := context.Background() + + got, err := s.GetUsername(ctx) + if err != nil { + t.Fatalf("GetUsername on empty store: %v", err) + } + if got != "" { + t.Errorf("expected empty username, got %q", got) + } + + if err := s.SetUsername(ctx, "alice"); err != nil { + t.Fatalf("SetUsername: %v", err) + } + got, err = s.GetUsername(ctx) + if err != nil { + t.Fatalf("GetUsername after set: %v", err) + } + if got != "alice" { + t.Errorf("Username round-trip: got %q, want alice", got) + } +} + +// TestSettingsManager_SetUsernameEmptyClears confirms that passing "" deletes +// the persisted username so the admin can effectively "forget" the cached +// owner on demand (e.g. after rotating the API key). +func TestSettingsManager_SetUsernameEmptyClears(t *testing.T) { + s, _ := newTestSettings() + ctx := context.Background() + if err := s.SetUsername(ctx, "alice"); err != nil { + t.Fatalf("SetUsername: %v", err) + } + if err := s.SetUsername(ctx, ""); err != nil { + t.Fatalf("SetUsername empty: %v", err) + } + got, err := s.GetUsername(ctx) + if err != nil { + t.Fatalf("GetUsername: %v", err) + } + if got != "" { + t.Errorf("expected cleared username, got %q", got) + } +} diff --git a/internal/db/db.go b/internal/db/db.go new file mode 100644 index 0000000..f706055 --- /dev/null +++ b/internal/db/db.go @@ -0,0 +1,24 @@ +// Package db vendors the minimal subset of the CMS-internal sqlc types the +// calcomblock plugin references for its settings storage. A standalone wasm +// plugin may not import git.dev.alexdunmow.com/block/cms/internal/db, so the +// two structs the plugin actually touches (only the Key + Value fields) are +// reproduced here. +// +// Provenance: cms/backend/internal/db/{models.go (Setting), +// settings.sql.go (UpsertSettingParams)}. The upstream structs also carry +// UpdatedAt/UpdatedBy columns; the plugin never reads them, so they are +// intentionally omitted from this vendored subset. +package db + +// Setting mirrors the CMS `settings` row as far as the plugin consumes it. +type Setting struct { + Key string `json:"key"` + Value []byte `json:"value"` +} + +// UpsertSettingParams mirrors the upstream sqlc UpsertSetting params the plugin +// constructs. +type UpsertSettingParams struct { + Key string `json:"key"` + Value []byte `json:"value"` +} diff --git a/internal/helpers/helpers.go b/internal/helpers/helpers.go new file mode 100644 index 0000000..3b6ea11 --- /dev/null +++ b/internal/helpers/helpers.go @@ -0,0 +1,197 @@ +// Package helpers vendors the small set of utility functions the calcomblock +// plugin previously imported from the CMS-internal package +// git.dev.alexdunmow.com/block/cms/internal/helpers. A standalone wasm plugin +// may only import git.dev.alexdunmow.com/block/core/... — never the CMS +// internals — so these pure, self-contained helpers are copied here verbatim +// (provenance: cms/backend/internal/helpers/{http,cleanup,maps,plugin_settings}.go). +// +// Re-copy on upstream changes; nothing here touches the CMS DB or capabilities. +package helpers + +import ( + "context" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "git.dev.alexdunmow.com/block/calcomblock/internal/db" +) + +// GetRealIP extracts the client IP from proxy headers, trusting the rightmost +// X-Forwarded-For entry (added by our trusted reverse proxy), then X-Real-IP, +// then CF-Connecting-IP, then RemoteAddr. +func GetRealIP(r *http.Request) string { + if xff := r.Header.Get("X-Forwarded-For"); xff != "" { + parts := strings.Split(xff, ",") + for i := len(parts) - 1; i >= 0; i-- { + ip := strings.TrimSpace(parts[i]) + if ip != "" { + return ip + } + } + } + if xri := r.Header.Get("X-Real-IP"); xri != "" { + return xri + } + if cfip := r.Header.Get("CF-Connecting-IP"); cfip != "" { + return cfip + } + ip := r.RemoteAddr + if colonIdx := strings.LastIndex(ip, ":"); colonIdx != -1 { + if !strings.Contains(ip, "[") || strings.Contains(ip[colonIdx:], "]") { + ip = ip[:colonIdx] + } + } + ip = strings.TrimPrefix(ip, "[") + ip = strings.TrimSuffix(ip, "]") + return ip +} + +// CleanupFunc is a function that performs cleanup work. +type CleanupFunc func() + +// StartCleanupLoop starts a background goroutine that calls cleanupFn at the +// given interval until the context is cancelled. +func StartCleanupLoop(ctx context.Context, interval time.Duration, cleanupFn CleanupFunc) { + go func() { + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + cleanupFn() + } + } + }() +} + +// GetStringOr returns a string value from a map, or defaultVal if not +// found/wrong type. +func GetStringOr(m map[string]any, key, defaultVal string) string { + if v, ok := m[key].(string); ok { + return v + } + return defaultVal +} + +// MaskSecret returns a masked version of a secret for display, showing the +// first 7 and last 4 characters. Example: "cal_live_1234567890abcdef" -> +// "cal_liv...cdef". +func MaskSecret(secret string) string { + if len(secret) <= 11 { + return "***" + } + return secret[:7] + "..." + secret[len(secret)-4:] +} + +// PluginSettingsQuerier is the interface required for plugin settings storage. +// The production implementation is capability-backed (see settings_store.go); +// tests inject an in-memory fake. +type PluginSettingsQuerier interface { + GetSetting(ctx context.Context, key string) (db.Setting, error) + UpsertSetting(ctx context.Context, arg db.UpsertSettingParams) (db.Setting, error) +} + +// GetPluginSettings retrieves settings for a plugin, stored under key +// "plugin:{pluginName}". Returns an empty map when none exist. +func GetPluginSettings(ctx context.Context, q PluginSettingsQuerier, pluginName string) (map[string]any, error) { + key := "plugin:" + pluginName + setting, err := q.GetSetting(ctx, key) + if err != nil { + return make(map[string]any), nil + } + var result map[string]any + if err := json.Unmarshal(setting.Value, &result); err != nil { + return nil, fmt.Errorf("failed to parse plugin settings: %w", err) + } + if result == nil { + result = make(map[string]any) + } + return result, nil +} + +// SetPluginSettings persists settings for a plugin under key +// "plugin:{pluginName}". +func SetPluginSettings(ctx context.Context, q PluginSettingsQuerier, pluginName string, settings map[string]any) error { + key := "plugin:" + pluginName + value, err := json.Marshal(settings) + if err != nil { + return fmt.Errorf("failed to serialize plugin settings: %w", err) + } + _, err = q.UpsertSetting(ctx, db.UpsertSettingParams{Key: key, Value: value}) + return err +} + +// PluginCrypto provides AES-256-GCM encryption/decryption for plugin secrets. +// The production path uses the SDK-provided crypto.Crypto; this is retained for +// the plugin's unit tests, which need a concrete implementation. +type PluginCrypto struct { + encryptionKey []byte +} + +// NewPluginCrypto creates a PluginCrypto whose key is padded/truncated to 32 +// bytes for AES-256. +func NewPluginCrypto(secretKey string) *PluginCrypto { + key := make([]byte, 32) + copy(key, []byte(secretKey)) + return &PluginCrypto{encryptionKey: key} +} + +// EncryptSecret encrypts a value with AES-256-GCM, returning hex. +func (c *PluginCrypto) EncryptSecret(plaintext string) (string, error) { + if plaintext == "" { + return "", nil + } + block, err := aes.NewCipher(c.encryptionKey) + if err != nil { + return "", fmt.Errorf("create cipher: %w", err) + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", fmt.Errorf("create gcm: %w", err) + } + nonce := make([]byte, gcm.NonceSize()) + if _, err := rand.Read(nonce); err != nil { + return "", fmt.Errorf("generate nonce: %w", err) + } + ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil) + return hex.EncodeToString(ciphertext), nil +} + +// DecryptSecret decrypts a hex AES-256-GCM ciphertext, falling back to the +// input verbatim for legacy unencrypted values. +func (c *PluginCrypto) DecryptSecret(cipherHex string) (string, error) { + if cipherHex == "" { + return "", nil + } + ciphertext, err := hex.DecodeString(cipherHex) + if err != nil { + return cipherHex, nil + } + block, err := aes.NewCipher(c.encryptionKey) + if err != nil { + return "", fmt.Errorf("create cipher: %w", err) + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", fmt.Errorf("create gcm: %w", err) + } + if len(ciphertext) < gcm.NonceSize() { + return cipherHex, nil + } + nonce := ciphertext[:gcm.NonceSize()] + ciphertext = ciphertext[gcm.NonceSize():] + plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return cipherHex, nil + } + return string(plaintext), nil +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..c27c31c --- /dev/null +++ b/main.go @@ -0,0 +1,13 @@ +//go:build wasip1 + +// Package main compiles the Cal.com Booking plugin to a wasip1 reactor-mode +// wasm guest. `ninja plugin build` produces the .bnp artifact the registry +// serves; the host runs `_initialize` once per pooled instance, invoking this +// init (hence wasmguest.Serve) before any ABI hook call. main is never called. +package main + +import "git.dev.alexdunmow.com/block/core/plugin/wasmguest" + +func init() { wasmguest.Serve(Registration) } + +func main() {} // never called — reactor mode diff --git a/phone_region.go b/phone_region.go new file mode 100644 index 0000000..6d62452 --- /dev/null +++ b/phone_region.go @@ -0,0 +1,123 @@ +package main + +import "strings" + +// regionFromLocale extracts the ISO 3166-1 alpha-2 region from a BCP-47 locale +// tag such as the value of navigator.language ("en-AU" -> "AU", "zh-Hans-CN" -> +// "CN"). The first subtag is the language unless it is itself an uppercase +// 2-letter region (a bare "AU"). Returns "" when no region subtag is present +// ("en" -> ""). +func regionFromLocale(locale string) string { + locale = strings.TrimSpace(locale) + if locale == "" { + return "" + } + parts := strings.FieldsFunc(locale, func(r rune) bool { return r == '-' || r == '_' }) + for i, part := range parts { + if len(part) != 2 || !isASCIILetters(part) { + continue // language (handled below), script ("Hans"), or variant + } + // Subtag 0 is the language unless it is already an uppercase region. + if i == 0 && part != strings.ToUpper(part) { + continue + } + return strings.ToUpper(part) + } + return "" +} + +// isASCIILetters reports whether s is non-empty and all ASCII letters. +func isASCIILetters(s string) bool { + if s == "" { + return false + } + for _, r := range s { + if (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') { + return false + } + } + return true +} + +// tzRegions maps IANA timezones to ISO 3166-1 alpha-2 regions for the common, +// unambiguous cases. The device timezone is a stronger locator than the UI +// locale (an en-US browser in Australia still reports Australia/*), so it is +// preferred. Ambiguous zones (most America/*) are intentionally omitted — those +// fall through to the locale. Australia/* is matched by prefix in +// regionFromTimezone. +var tzRegions = map[string]string{ + "Pacific/Auckland": "NZ", "Pacific/Chatham": "NZ", + "Europe/London": "GB", "Europe/Dublin": "IE", "Europe/Paris": "FR", + "Europe/Berlin": "DE", "Europe/Madrid": "ES", "Europe/Rome": "IT", + "Europe/Amsterdam": "NL", "Europe/Brussels": "BE", "Europe/Zurich": "CH", + "Europe/Stockholm": "SE", "Europe/Oslo": "NO", "Europe/Copenhagen": "DK", + "Asia/Singapore": "SG", "Asia/Kolkata": "IN", "Asia/Calcutta": "IN", + "Asia/Tokyo": "JP", "Asia/Hong_Kong": "HK", "Asia/Dubai": "AE", + "Asia/Shanghai": "CN", "Asia/Seoul": "KR", "Africa/Johannesburg": "ZA", +} + +// regionFromTimezone maps an IANA timezone (navigator-detected, e.g. +// "Australia/Perth") to a region for the common cases. Returns "" for +// unknown/ambiguous zones (including the UTC fallback some browsers report). +func regionFromTimezone(tz string) string { + tz = strings.TrimSpace(tz) + if tz == "" { + return "" + } + if strings.HasPrefix(tz, "Australia/") { + return "AU" + } + return tzRegions[tz] +} + +// resolveRegion picks the ISO region used to pre-select the phone country +// combobox and to seed E.164 normalization when the visitor has not explicitly +// picked a country. It prefers the timezone region (strongest locator), then the +// browser locale region, validated against the known country list. Returns "" +// when neither is known — the combobox then has no default and normalizePhone +// only handles already-international numbers. +func resolveRegion(locale, timezone string) string { + for _, r := range []string{regionFromTimezone(timezone), regionFromLocale(locale)} { + if r != "" { + if _, ok := countryByCode(r); ok { + return r + } + } + } + return "" +} + +// countryByCode returns the country with the given ISO alpha-2 code, or false. +func countryByCode(code string) (Country, bool) { + code = strings.ToUpper(strings.TrimSpace(code)) + for _, c := range countries { + if c.Code == code { + return c, true + } + } + return Country{}, false +} + +// countryFlag returns the emoji flag for a 2-letter ISO region code by mapping +// each letter to its Unicode regional-indicator symbol. "" for invalid codes. +func countryFlag(code string) string { + code = strings.ToUpper(strings.TrimSpace(code)) + if len(code) != 2 || !isASCIILetters(code) { + return "" + } + r := []rune(code) + return string(rune(0x1F1E6+(r[0]-'A'))) + string(rune(0x1F1E6+(r[1]-'A'))) +} + +// countryLabel renders the human label shown in the combobox and its pre-fill, +// e.g. "🇦🇺 Australia (+61)". Returns "" when the code is unknown. +func countryLabel(code string) string { + c, ok := countryByCode(code) + if !ok { + return "" + } + if flag := countryFlag(c.Code); flag != "" { + return flag + " " + c.Name + " (" + c.Dial + ")" + } + return c.Name + " (" + c.Dial + ")" +} diff --git a/plugin.mod b/plugin.mod new file mode 100644 index 0000000..e2997b6 --- /dev/null +++ b/plugin.mod @@ -0,0 +1,9 @@ +[plugin] +name = "calcomblock" +display_name = "Cal.com Booking" +scope = "ninja" +version = "2.0.0" +description = "Embeddable Cal.com booking calendar block with custom styling, timezone-aware slot windowing, honeypot + captcha + rate-limited public booking endpoints, and webhook receiver." +kind = "plugin" +categories = ["forms"] +tags = ["calcom", "booking", "calendar", "scheduling", "appointments"] diff --git a/ratelimit.go b/ratelimit.go new file mode 100644 index 0000000..3715f36 --- /dev/null +++ b/ratelimit.go @@ -0,0 +1,61 @@ +package main + +import ( + "sync" + "time" +) + +// RateLimiter is a per-IP fixed-window counter used to throttle the public +// /book endpoint. It is intentionally simple — in-memory, single-process — +// matching the single-tenant deployment model. A multi-replica deployment +// would need a shared store (Redis or DB) before this limiter is meaningful. +type RateLimiter struct { + mu sync.Mutex + buckets map[string]*rlBucket + perHour int +} + +type rlBucket struct { + count int + resetsAt time.Time +} + +// NewRateLimiter returns a limiter that allows perHour requests per IP in any +// rolling 1-hour window (resetting after the first request in a fresh window). +func NewRateLimiter(perHour int) *RateLimiter { + return &RateLimiter{ + buckets: make(map[string]*rlBucket), + perHour: perHour, + } +} + +// Allow consumes one allowance for ip; returns false when the window's quota +// is exhausted. +func (r *RateLimiter) Allow(ip string) bool { + r.mu.Lock() + defer r.mu.Unlock() + now := time.Now() + b := r.buckets[ip] + if b == nil || now.After(b.resetsAt) { + r.buckets[ip] = &rlBucket{count: 1, resetsAt: now.Add(time.Hour)} + return true + } + if b.count >= r.perHour { + return false + } + b.count++ + return true +} + +// SweepStale drops buckets whose window has elapsed. Driven by +// helpers.StartCleanupLoop on a periodic schedule. +func (r *RateLimiter) SweepStale() { + r.mu.Lock() + defer r.mu.Unlock() + now := time.Now() + for ip, b := range r.buckets { + if now.After(b.resetsAt) { + delete(r.buckets, ip) + } + } +} diff --git a/ratelimit_test.go b/ratelimit_test.go new file mode 100644 index 0000000..af4a2b6 --- /dev/null +++ b/ratelimit_test.go @@ -0,0 +1,85 @@ +package main + +import ( + "testing" + "time" +) + +// TestRateLimiter_AllowsUpToCapAndBlocksRest verifies the WO-018 contract: +// 10 calls from the same IP succeed; the 11th is denied. +func TestRateLimiter_AllowsUpToCapAndBlocksRest(t *testing.T) { + rl := NewRateLimiter(10) + + for i := 1; i <= 10; i++ { + if !rl.Allow("203.0.113.1") { + t.Fatalf("call %d unexpectedly denied", i) + } + } + if rl.Allow("203.0.113.1") { + t.Fatalf("11th call must be denied") + } +} + +// TestRateLimiter_IsolatesByIP confirms that buckets are keyed per-IP. +func TestRateLimiter_IsolatesByIP(t *testing.T) { + rl := NewRateLimiter(2) + for i := range 2 { + if !rl.Allow("a") { + t.Fatalf("call %d from 'a' should succeed", i+1) + } + } + if rl.Allow("a") { + t.Fatalf("third call from 'a' should be denied") + } + if !rl.Allow("b") { + t.Fatalf("first call from 'b' should succeed even after 'a' is full") + } +} + +// TestRateLimiter_WindowResetsAfterHour verifies the fixed-window semantics: +// once a bucket's resetsAt is in the past, the next Allow() call starts a +// fresh window with count=1. This guards against a regression where the +// limiter forgets to clear/reset stale buckets and locks out legitimate +// traffic indefinitely. +// +// We exercise this by mutating the bucket's resetsAt directly to a time in +// the past — equivalent to fast-forwarding the clock without waiting an hour. +// The helper accesses the unexported buckets map; this is safe because +// _test.go files compile inside the same package. +func TestRateLimiter_WindowResetsAfterHour(t *testing.T) { + rl := NewRateLimiter(3) + const ip = "198.51.100.7" + + // Fill the bucket. + for i := range 3 { + if !rl.Allow(ip) { + t.Fatalf("call %d should succeed", i+1) + } + } + if rl.Allow(ip) { + t.Fatalf("4th call should be denied (bucket full)") + } + + // Fast-forward the clock by pushing resetsAt into the past. + rl.mu.Lock() + b, ok := rl.buckets[ip] + if !ok { + rl.mu.Unlock() + t.Fatalf("bucket missing for ip %s after filling", ip) + } + b.resetsAt = time.Now().Add(-time.Hour - time.Second) + rl.mu.Unlock() + + // Next call must reset the window and succeed. + if !rl.Allow(ip) { + t.Fatalf("Allow() must reset the bucket after window expiry — got false") + } + + // Bucket count must be 1 (fresh window), not 4. + rl.mu.Lock() + got := rl.buckets[ip].count + rl.mu.Unlock() + if got != 1 { + t.Errorf("bucket count after reset: got %d, want 1 (fresh window)", got) + } +} diff --git a/register.go b/register.go new file mode 100644 index 0000000..d0fa262 --- /dev/null +++ b/register.go @@ -0,0 +1,64 @@ +package main + +import ( + "embed" + "io/fs" + "net/http" + + "git.dev.alexdunmow.com/block/core/blocks" + "git.dev.alexdunmow.com/block/core/plugin" + "git.dev.alexdunmow.com/block/core/templates" +) + +//go:embed all:web/dist/assets +var distAssets embed.FS + +//go:embed schemas +var schemasFS embed.FS + +//go:embed plugin.mod +var pluginModBytes []byte + +// Register is the plugin entry point. +// Returns an error if registration fails. +func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error { + br.Register(CalcomBookingMeta, CalcomBookingFunc) + return nil +} + +// CalcomBookingMeta defines the Cal.com booking block metadata +var CalcomBookingMeta = blocks.BlockMeta{ + Key: "calcom:booking", + Title: "Cal.com Booking", + Description: "Embeddable booking calendar with custom styling powered by Cal.com", + Category: blocks.CategoryContent, + Source: "calcomblock", + EditorJS: "remoteEntry.js", +} + +// Schemas returns the embedded schemas filesystem +func Schemas() fs.FS { + subFS, _ := fs.Sub(schemasFS, "schemas") + return subFS +} + +// AssetsHandler serves static assets including the Module Federation remote entry +func AssetsHandler() http.Handler { + subFS, err := fs.Sub(distAssets, "web/dist/assets") + if err != nil { + return http.NotFoundHandler() + } + return http.FileServer(http.FS(subFS)) +} + +// HTTPHandler returns the plugin's HTTP handler for Cal.com API endpoints. +// Mounted at /api/plugins/calcomblock/. deps carries Crypto + Pool. +func HTTPHandler(deps plugin.CoreServices) http.Handler { + return NewCalcomRouter(deps) +} + +// SettingsPanel returns the path to the Module Federation settings component. +// The settings.tsx component is exposed at ./settings in the remoteEntry.js +func SettingsPanel() string { + return "remoteEntry.js" +} diff --git a/registration.go b/registration.go new file mode 100644 index 0000000..990321e --- /dev/null +++ b/registration.go @@ -0,0 +1,23 @@ +package main + +import ( + "io/fs" + "net/http" + + "git.dev.alexdunmow.com/block/core/blocks" + "git.dev.alexdunmow.com/block/core/plugin" + "git.dev.alexdunmow.com/block/core/templates" +) + +// Registration is the compile-time plugin registration for the Cal.com Booking block. +var Registration = plugin.PluginRegistration{ + Name: "calcomblock", + Version: plugin.ParseModVersion(pluginModBytes), + Register: func(tr templates.TemplateRegistry, br blocks.BlockRegistry) error { + return Register(tr, br) + }, + Assets: func() http.Handler { return AssetsHandler() }, + Schemas: func() fs.FS { return Schemas() }, + HTTPHandler: func(deps plugin.CoreServices) http.Handler { return NewCalcomRouter(deps) }, + SettingsPanel: func() string { return SettingsPanel() }, +} diff --git a/schemas/calcom-booking.schema.json b/schemas/calcom-booking.schema.json new file mode 100644 index 0000000..98a1e8d --- /dev/null +++ b/schemas/calcom-booking.schema.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "Cal.com Booking", + "description": "Embeddable booking calendar powered by Cal.com", + "properties": { + "username": { + "type": "string", + "title": "Cal.com Username", + "description": "Your Cal.com username (e.g., 'johndoe')", + "x-editor": "text" + }, + "eventTypeSlug": { + "type": "string", + "title": "Event Type", + "description": "The event type slug (e.g., '30min', 'consultation')", + "x-editor": "text" + }, + "title": { + "type": "string", + "title": "Widget Title", + "description": "Title shown above the booking widget", + "x-editor": "text" + }, + "description": { + "type": "string", + "title": "Description", + "description": "Optional description text", + "x-editor": "textarea" + }, + "weeksToShow": { + "type": "integer", + "title": "Weeks to Display", + "description": "Number of weeks to show in the calendar", + "default": 2, + "minimum": 1, + "maximum": 8, + "x-editor": "number" + }, + "showTimezone": { + "type": "boolean", + "title": "Show Timezone", + "description": "Allow visitors to select their timezone", + "default": true, + "x-editor": "checkbox" + }, + "captchaEnabled": { + "type": "boolean", + "title": "Enable captcha", + "description": "Require visitors to solve a privacy-friendly proof-of-work captcha before booking (spam protection)", + "default": false, + "x-editor": "checkbox" + } + }, + "required": ["eventTypeSlug"] +} diff --git a/settings_store.go b/settings_store.go new file mode 100644 index 0000000..dee7540 --- /dev/null +++ b/settings_store.go @@ -0,0 +1,247 @@ +package main + +import ( + "context" + "crypto/rand" + "encoding/hex" + "encoding/json" + "strings" + "time" + + "git.dev.alexdunmow.com/block/core/crypto" + "git.dev.alexdunmow.com/block/core/settings" + + "git.dev.alexdunmow.com/block/calcomblock/internal/db" + "git.dev.alexdunmow.com/block/calcomblock/internal/helpers" +) + +// SettingsManager persists Cal.com plugin settings through the SDK settings +// capabilities (a capabilityQuerier satisfying helpers.PluginSettingsQuerier in +// production; an in-memory fake in tests). Secrets are encrypted using the +// SDK-provided crypto.Crypto interface. +type SettingsManager struct { + crypto crypto.Crypto + queries helpers.PluginSettingsQuerier +} + +// NewSettingsManager constructs a SettingsManager backed by the given crypto +// and querier. The querier must satisfy helpers.PluginSettingsQuerier. +func NewSettingsManager(c crypto.Crypto, q helpers.PluginSettingsQuerier) *SettingsManager { + return &SettingsManager{crypto: c, queries: q} +} + +// GetAPIKey returns the decrypted Cal.com API key, or empty string if not configured. +func (m *SettingsManager) GetAPIKey(ctx context.Context) (string, error) { + s, err := helpers.GetPluginSettings(ctx, m.queries, "calcomblock") + if err != nil { + return "", err + } + enc := helpers.GetStringOr(s, "api_key_encrypted", "") + if enc == "" { + return "", nil + } + return m.crypto.DecryptSecret(enc) +} + +// SetAPIKey encrypts and stores the Cal.com API key. Pass an empty string to clear. +func (m *SettingsManager) SetAPIKey(ctx context.Context, key string) error { + s, err := helpers.GetPluginSettings(ctx, m.queries, "calcomblock") + if err != nil { + return err + } + if key == "" { + delete(s, "api_key_encrypted") + } else { + enc, err := m.crypto.EncryptSecret(key) + if err != nil { + return err + } + s["api_key_encrypted"] = enc + } + return helpers.SetPluginSettings(ctx, m.queries, "calcomblock", s) +} + +// GetSettings returns the full settings blob. Secrets are NOT decrypted here — +// the caller is responsible for not exposing `api_key_encrypted` directly. +func (m *SettingsManager) GetSettings(ctx context.Context) (map[string]any, error) { + return helpers.GetPluginSettings(ctx, m.queries, "calcomblock") +} + +// GetUsername returns the Cal.com account username associated with the +// configured API key. Stored as plain text (not a secret); populated by +// HandleSaveSettings via CalcomClient.GetMe. Empty string when the API key +// has never been saved or the /me lookup has not yet succeeded. +func (m *SettingsManager) GetUsername(ctx context.Context) (string, error) { + s, err := helpers.GetPluginSettings(ctx, m.queries, "calcomblock") + if err != nil { + return "", err + } + return helpers.GetStringOr(s, "username", ""), nil +} + +// SetUsername persists the Cal.com account username. Pass an empty string to +// clear (e.g. when the API key is removed). +func (m *SettingsManager) SetUsername(ctx context.Context, username string) error { + s, err := helpers.GetPluginSettings(ctx, m.queries, "calcomblock") + if err != nil { + return err + } + if username == "" { + delete(s, "username") + } else { + s["username"] = username + } + return helpers.SetPluginSettings(ctx, m.queries, "calcomblock", s) +} + +// GetSiteTimezone returns the site-wide IANA timezone configured under Site +// Settings → Localization, or "" when unset. Used as the fallback display +// timezone for booking times when the visitor's request carries none. +// +// Reads the "site" settings key through the querier, which resolves it via the +// SDK settings capability (settings.Settings.GetSiteSettings) host-side. +func (m *SettingsManager) GetSiteTimezone(ctx context.Context) string { + setting, err := m.queries.GetSetting(ctx, "site") + if err != nil { + return "" + } + var site struct { + Localization struct { + Timezone string `json:"timezone"` + } `json:"localization"` + } + if err := json.Unmarshal(setting.Value, &site); err != nil { + return "" + } + return strings.TrimSpace(site.Localization.Timezone) +} + +// resolveLocation resolves the IANA timezone used to window and display +// booking times. Resolution order: the visitor-supplied name (browser-detected, +// user-controlled input) when it parses, else the site-wide Localization +// timezone from site settings, else UTC. A literal "UTC" request is treated as +// unset: it is the widget's never-updated sentinel default (pages cached from +// before timezone detection, no-JS visitors, bots), so the site timezone is a +// strictly better guess for a human visitor. +// +// Returns the location plus its canonical name for forwarding to Cal.com. +// Lives on SettingsManager (not the handler) so the initial block render — +// which has no *http.Request — can resolve via the same ladder. +func (m *SettingsManager) resolveLocation(ctx context.Context, requested string) (*time.Location, string) { + for _, name := range []string{strings.TrimSpace(requested), m.GetSiteTimezone(ctx)} { + if name == "" || strings.EqualFold(name, "UTC") { + continue + } + if loc, err := time.LoadLocation(name); err == nil { + return loc, name + } + } + return time.UTC, "UTC" +} + +// GetWebhookSecret returns the decrypted webhook HMAC secret, or empty if none +// has been provisioned yet. +func (m *SettingsManager) GetWebhookSecret(ctx context.Context) (string, error) { + s, err := helpers.GetPluginSettings(ctx, m.queries, "calcomblock") + if err != nil { + return "", err + } + enc := helpers.GetStringOr(s, "webhook_secret_encrypted", "") + if enc == "" { + return "", nil + } + return m.crypto.DecryptSecret(enc) +} + +// SetWebhookSecret encrypts and persists the webhook HMAC secret. Pass an +// empty string to clear. +func (m *SettingsManager) SetWebhookSecret(ctx context.Context, secret string) error { + s, err := helpers.GetPluginSettings(ctx, m.queries, "calcomblock") + if err != nil { + return err + } + if secret == "" { + delete(s, "webhook_secret_encrypted") + } else { + enc, err := m.crypto.EncryptSecret(secret) + if err != nil { + return err + } + s["webhook_secret_encrypted"] = enc + } + return helpers.SetPluginSettings(ctx, m.queries, "calcomblock", s) +} + +// UpdateLastEvent records that the webhook handler received a Cal.com event +// at the given time. Surfaced to the admin settings panel via GetSettings. +func (m *SettingsManager) UpdateLastEvent(ctx context.Context, triggerEvent, createdAt string) error { + s, err := helpers.GetPluginSettings(ctx, m.queries, "calcomblock") + if err != nil { + return err + } + s["last_event_type"] = triggerEvent + s["last_event_at"] = createdAt + return helpers.SetPluginSettings(ctx, m.queries, "calcomblock", s) +} + +// generateWebhookSecret returns a fresh 32-byte secret encoded as a 64-char +// hex string. Cryptographically random via crypto/rand. +func generateWebhookSecret() (string, error) { + buf := make([]byte, 32) + if _, err := rand.Read(buf); err != nil { + return "", err + } + return hex.EncodeToString(buf), nil +} + +// capabilityQuerier adapts the SDK settings capabilities to +// helpers.PluginSettingsQuerier. Under the wasm ABI a plugin's Postgres role is +// sandboxed to its OWN schema, so the CMS `settings` table (public schema) is +// unreachable by direct SQL — settings persistence MUST go through the +// host-side capability interfaces (settings.Settings / settings.Updater), which +// perform the DB work on the host's behalf. +// +// It recognises the two keys the settings helpers request: "plugin:" +// (round-tripped through Get/UpdatePluginSettings) and "site" (read-only via +// GetSiteSettings). Values are JSON, matching the shape the helpers expect from +// the old `settings.value` bytea column. +type capabilityQuerier struct { + settings settings.Settings + updater settings.Updater +} + +func (q *capabilityQuerier) GetSetting(ctx context.Context, key string) (db.Setting, error) { + var ( + m map[string]any + err error + ) + if key == "site" { + m, err = q.settings.GetSiteSettings(ctx) + } else { + m, err = q.settings.GetPluginSettings(ctx, strings.TrimPrefix(key, "plugin:")) + } + if err != nil { + return db.Setting{}, err + } + value, err := json.Marshal(m) + if err != nil { + return db.Setting{}, err + } + return db.Setting{Key: key, Value: value}, nil +} + +func (q *capabilityQuerier) UpsertSetting(ctx context.Context, arg db.UpsertSettingParams) (db.Setting, error) { + var m map[string]any + if len(arg.Value) > 0 { + if err := json.Unmarshal(arg.Value, &m); err != nil { + return db.Setting{}, err + } + } + if m == nil { + m = map[string]any{} + } + if err := q.updater.UpdatePluginSettings(ctx, strings.TrimPrefix(arg.Key, "plugin:"), m); err != nil { + return db.Setting{}, err + } + return db.Setting(arg), nil +} diff --git a/timezone_test.go b/timezone_test.go new file mode 100644 index 0000000..1143644 --- /dev/null +++ b/timezone_test.go @@ -0,0 +1,400 @@ +package main + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" +) + +// These tests pin the timezone contract for the public booking flow: every +// surface (slot list, form recap, confirmation, attendee record) renders in +// the MEETING's timezone — the event's Cal.com availability schedule — never +// the visitor's. Ladder: event schedule timezone → site Localization setting +// → UTC. +// +// Written against the bidbuddy.com.au incidents (2026-06-10). First: the +// handler windowed and rendered slots in UTC (Perth visitors saw "12:00 AM"), +// leaked the next day (Cal.com's end bound is date-granular), and assembled +// slots from an unsorted map range. Second: display depended on client-side +// timezone detection, which shipped a never-updated "UTC" sentinel on cached +// pages. The meeting timezone is a server-side fact; visitor state is out. + +// seedSiteTimezone writes a site settings blob with the given localization +// timezone into the fake querier, mirroring Site Settings → Localization. +func seedSiteTimezone(q *fakeQuerier, tz string) { + q.mu.Lock() + defer q.mu.Unlock() + q.data["site"] = []byte(`{"localization":{"timezone":"` + tz + `"}}`) +} + +// fakeCalcom builds a Cal.com test double serving the event-timezone +// resolution pair — /event-types (scheduleId: null → default schedule) and +// /schedules (the schedule carrying scheduleTZ) — and delegates every other +// path (slots, bookings) to rest. scheduleTZ "" makes /schedules fail with a +// 500, simulating Cal.com being unreachable for the timezone lookup. The +// double is installed as the package base URL for the duration of the test. +func fakeCalcom(t *testing.T, scheduleTZ string, rest http.HandlerFunc) *httptest.Server { + t.Helper() + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case strings.HasPrefix(r.URL.Path, "/event-types"): + _, _ = io.WriteString(w, `{"status":"success","data":[{"id":1,"slug":"30min","scheduleId":null,"bookingFields":[]}]}`) + case strings.HasPrefix(r.URL.Path, "/schedules"): + if scheduleTZ == "" { + w.WriteHeader(http.StatusInternalServerError) + return + } + _, _ = io.WriteString(w, `{"status":"success","data":[{"id":9,"name":"Availability","timeZone":"`+scheduleTZ+`","isDefault":true}]}`) + default: + if rest != nil { + rest(w, r) + return + } + _, _ = io.WriteString(w, `{"status":"success","data":{}}`) + } + })) + t.Cleanup(srv.Close) + withCalcomBaseURL(t, srv.URL) + return srv +} + +// newKeyedHandler returns a handler whose settings carry an API key — the +// precondition for the event-timezone fetch. +func newKeyedHandler(t *testing.T) (*CalcomHandler, *fakeQuerier) { + t.Helper() + h, q := newTestHandler() + if err := h.settings.SetAPIKey(context.Background(), "cal_live_test"); err != nil { + t.Fatalf("SetAPIKey: %v", err) + } + return h, q +} + +// TestHandleGetSlots_WindowBuiltInEventTimezone asserts the [start, end) +// window sent to Cal.com covers the selected date in the MEETING's timezone — +// resolved from the event's availability schedule, with no timezone hint on +// the request at all. For date=2026-06-11 and a Perth schedule the window +// must be 2026-06-11T00:00:00+08:00 .. 2026-06-12T00:00:00+08:00. +func TestHandleGetSlots_WindowBuiltInEventTimezone(t *testing.T) { + var gotQuery url.Values + fakeCalcom(t, "Australia/Perth", func(w http.ResponseWriter, r *http.Request) { + gotQuery = r.URL.Query() + _, _ = io.WriteString(w, `{"status":"success","data":{}}`) + }) + h, _ := newKeyedHandler(t) + + req := httptest.NewRequest(http.MethodGet, + "/slots?username=alice&eventType=30min&date=2026-06-11&blockId=b1", nil) + rec := httptest.NewRecorder() + h.HandleGetSlots(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + if got := gotQuery.Get("start"); got != "2026-06-11T00:00:00+08:00" { + t.Errorf("outbound start: got %q, want 2026-06-11T00:00:00+08:00", got) + } + if got := gotQuery.Get("end"); got != "2026-06-12T00:00:00+08:00" { + t.Errorf("outbound end: got %q, want 2026-06-12T00:00:00+08:00", got) + } + if got := gotQuery.Get("timeZone"); got != "Australia/Perth" { + t.Errorf("outbound timeZone: got %q, want Australia/Perth", got) + } +} + +// TestHandleGetSlots_VisitorTimezoneParamIgnored pins the design decision: a +// timezone query param — whether a visitor's real browser zone or a stale +// cached page's sentinel — must NOT move the display away from the meeting's +// timezone. Availability is a fact about the host's calendar. +func TestHandleGetSlots_VisitorTimezoneParamIgnored(t *testing.T) { + var gotQuery url.Values + fakeCalcom(t, "Australia/Perth", func(w http.ResponseWriter, r *http.Request) { + gotQuery = r.URL.Query() + _, _ = io.WriteString(w, `{"status":"success","data":{}}`) + }) + h, _ := newKeyedHandler(t) + + req := httptest.NewRequest(http.MethodGet, + "/slots?username=alice&eventType=30min&date=2026-06-11&blockId=b1&timezone=America/New_York", nil) + rec := httptest.NewRecorder() + h.HandleGetSlots(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + if got := gotQuery.Get("timeZone"); got != "Australia/Perth" { + t.Errorf("outbound timeZone: got %q, want Australia/Perth (visitor param must be ignored)", got) + } + if got := gotQuery.Get("start"); got != "2026-06-11T00:00:00+08:00" { + t.Errorf("outbound start: got %q, want Perth-midnight window despite visitor param", got) + } +} + +// TestHandleGetSlots_FiltersSlotsOutsideEventDay reproduces the production +// leak: Cal.com treats the end bound date-granularly and returns the entire +// next day too. Slots not falling on the requested date in the meeting's +// timezone must be dropped, not rendered under the wrong heading (where +// same-wall-clock times read as duplicates). +func TestHandleGetSlots_FiltersSlotsOutsideEventDay(t *testing.T) { + fakeCalcom(t, "Australia/Perth", func(w http.ResponseWriter, r *http.Request) { + // Perth-local response shape with a leaked next-day group, as observed + // live on 2026-06-10 against api.cal.com. + _, _ = io.WriteString(w, `{ + "status":"success", + "data":{ + "2026-06-11":[{"start":"2026-06-11T08:00:00.000+08:00"},{"start":"2026-06-11T17:30:00.000+08:00"}], + "2026-06-12":[{"start":"2026-06-12T13:30:00.000+08:00"}] + } + }`) + }) + h, _ := newKeyedHandler(t) + + req := httptest.NewRequest(http.MethodGet, + "/slots?username=alice&eventType=30min&date=2026-06-11&blockId=b1", nil) + rec := httptest.NewRecorder() + h.HandleGetSlots(rec, req) + + body := rec.Body.String() + if !strings.Contains(body, "8:00 AM") { + t.Errorf("expected '8:00 AM' (Perth-local slot) in body, got: %q", body) + } + if !strings.Contains(body, "5:30 PM") { + t.Errorf("expected '5:30 PM' (Perth-local slot) in body, got: %q", body) + } + if strings.Contains(body, "1:30 PM") { + t.Errorf("next-day slot (2026-06-12 13:30) leaked into the 2026-06-11 view: %q", body) + } +} + +// TestHandleGetSlots_ConvertsUTCSlotsToEventTimezone asserts display times are +// converted into the meeting's timezone even when Cal.com returns UTC (Z) +// timestamps — display must never depend on the offset Cal.com happens to +// format with. +func TestHandleGetSlots_ConvertsUTCSlotsToEventTimezone(t *testing.T) { + fakeCalcom(t, "Australia/Perth", func(w http.ResponseWriter, r *http.Request) { + // 2026-06-11 00:00Z == 8:00 AM Perth; 09:30Z == 5:30 PM Perth. + _, _ = io.WriteString(w, `{ + "status":"success", + "data":{"2026-06-11":[{"start":"2026-06-11T00:00:00.000Z"},{"start":"2026-06-11T09:30:00.000Z"}]} + }`) + }) + h, _ := newKeyedHandler(t) + + req := httptest.NewRequest(http.MethodGet, + "/slots?username=alice&eventType=30min&date=2026-06-11&blockId=b1", nil) + rec := httptest.NewRecorder() + h.HandleGetSlots(rec, req) + + body := rec.Body.String() + if !strings.Contains(body, "8:00 AM") { + t.Errorf("expected UTC slot rendered as '8:00 AM' Perth time, got: %q", body) + } + if !strings.Contains(body, "5:30 PM") { + t.Errorf("expected UTC slot rendered as '5:30 PM' Perth time, got: %q", body) + } + if strings.Contains(body, "12:00 AM") { + t.Errorf("slot rendered as raw UTC wall time ('12:00 AM') instead of Perth time: %q", body) + } +} + +// TestHandleGetSlots_SortsSlotsChronologically guards against Go map iteration +// order: a Perth-local day can span two UTC date keys, so slots assembled +// across keys must be explicitly sorted. (2026-06-10T23:00Z == 7:00 AM Perth +// on June 11; 2026-06-11T01:00:00Z == 9:00 AM Perth on June 11.) +func TestHandleGetSlots_SortsSlotsChronologically(t *testing.T) { + fakeCalcom(t, "Australia/Perth", func(w http.ResponseWriter, r *http.Request) { + _, _ = io.WriteString(w, `{ + "status":"success", + "data":{ + "2026-06-10":[{"start":"2026-06-10T23:00:00.000Z"}], + "2026-06-11":[{"start":"2026-06-11T01:00:00.000Z"}] + } + }`) + }) + h, _ := newKeyedHandler(t) + + req := httptest.NewRequest(http.MethodGet, + "/slots?username=alice&eventType=30min&date=2026-06-11&blockId=b1", nil) + rec := httptest.NewRecorder() + h.HandleGetSlots(rec, req) + + body := rec.Body.String() + early := strings.Index(body, "7:00 AM") + late := strings.Index(body, "9:00 AM") + if early == -1 || late == -1 { + t.Fatalf("expected both '7:00 AM' and '9:00 AM' in body, got: %q", body) + } + if early > late { + t.Errorf("slots out of chronological order: '7:00 AM' rendered after '9:00 AM'") + } +} + +// TestHandleGetSlots_FallsBackToSiteTimezone asserts that when the event +// timezone can't be fetched (Cal.com schedules endpoint down), the site-wide +// Localization timezone is used instead of UTC. +func TestHandleGetSlots_FallsBackToSiteTimezone(t *testing.T) { + var gotQuery url.Values + fakeCalcom(t, "" /* schedules endpoint fails */, func(w http.ResponseWriter, r *http.Request) { + gotQuery = r.URL.Query() + _, _ = io.WriteString(w, `{"status":"success","data":{}}`) + }) + h, q := newKeyedHandler(t) + seedSiteTimezone(q, "Australia/Perth") + + req := httptest.NewRequest(http.MethodGet, + "/slots?username=alice&eventType=30min&date=2026-06-11&blockId=b1", nil) + rec := httptest.NewRecorder() + h.HandleGetSlots(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + if got := gotQuery.Get("timeZone"); got != "Australia/Perth" { + t.Errorf("outbound timeZone: got %q, want Australia/Perth (site fallback)", got) + } + if got := gotQuery.Get("start"); got != "2026-06-11T00:00:00+08:00" { + t.Errorf("outbound start: got %q, want Perth-midnight window", got) + } +} + +// TestHandleGetSlots_InvalidEventTimezoneFallsBack asserts a garbage timezone +// on the Cal.com schedule degrades to the site timezone rather than erroring +// or silently using the bogus value. +func TestHandleGetSlots_InvalidEventTimezoneFallsBack(t *testing.T) { + var gotQuery url.Values + fakeCalcom(t, "Not/AZone", func(w http.ResponseWriter, r *http.Request) { + gotQuery = r.URL.Query() + _, _ = io.WriteString(w, `{"status":"success","data":{}}`) + }) + h, q := newKeyedHandler(t) + seedSiteTimezone(q, "Australia/Perth") + + req := httptest.NewRequest(http.MethodGet, + "/slots?username=alice&eventType=30min&date=2026-06-11&blockId=b1", nil) + rec := httptest.NewRecorder() + h.HandleGetSlots(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d (body: %s)", rec.Code, rec.Body.String()) + } + if got := gotQuery.Get("timeZone"); got != "Australia/Perth" { + t.Errorf("outbound timeZone: got %q, want Australia/Perth (invalid event tz must fall back)", got) + } +} + +// TestHandleGetForm_DisplayTimeInEventTimezone asserts the form's "selected +// time" recap renders in the meeting's timezone when the slot start arrives +// as a UTC timestamp. +func TestHandleGetForm_DisplayTimeInEventTimezone(t *testing.T) { + fakeCalcom(t, "Australia/Perth", nil) + h, _ := newKeyedHandler(t) + + // 2026-06-11T01:00:00Z == 9:00 AM Australia/Perth. + req := httptest.NewRequest(http.MethodGet, + "/form?blockId=b1&start=2026-06-11T01:00:00Z&date=2026-06-11&username=alice&eventType=30min", nil) + rec := httptest.NewRecorder() + h.HandleGetForm(rec, req) + + body := rec.Body.String() + if !strings.Contains(body, "9:00 AM") { + t.Errorf("expected form recap '9:00 AM' (Perth), got: %q", body) + } + if strings.Contains(body, "1:00 AM") { + t.Errorf("form recap rendered raw UTC wall time ('1:00 AM'): %q", body) + } +} + +// TestHandleCreateBooking_ConfirmationInEventTimezone asserts the booking +// confirmation panel renders the meeting time in the meeting's timezone, and +// that the attendee record sent to Cal.com carries the meeting timezone too — +// even when a stale cached page still submits a visitor timezone field. +func TestHandleCreateBooking_ConfirmationInEventTimezone(t *testing.T) { + var attendeeTZ string + fakeCalcom(t, "Australia/Perth", func(w http.ResponseWriter, r *http.Request) { + if strings.HasPrefix(r.URL.Path, "/bookings") { + var payload struct { + Attendee struct { + TimeZone string `json:"timeZone"` + } `json:"attendee"` + } + _ = json.NewDecoder(r.Body).Decode(&payload) + attendeeTZ = payload.Attendee.TimeZone + _, _ = io.WriteString(w, `{ + "status":"success", + "data":{"uid":"uid-1","start":"2026-06-11T01:00:00Z","end":"2026-06-11T01:30:00Z","location":"https://meet.example.com/uid-1"} + }`) + return + } + _, _ = io.WriteString(w, `{"status":"success","data":{}}`) + }) + h, _ := newKeyedHandler(t) + + form := url.Values{ + "blockId": {"b1"}, + "username": {"alice"}, + "eventType": {"30min"}, + "start": {"2026-06-11T01:00:00Z"}, + "name": {"Sue Findlay"}, + "email": {"sue@example.test"}, + // A page cached before the rework still posts this — must be ignored. + "timezone": {"America/New_York"}, + } + req := httptest.NewRequest(http.MethodPost, "/book", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + rec := httptest.NewRecorder() + h.HandleCreateBooking(rec, req) + + body := rec.Body.String() + // 01:00Z on June 11 is 9:00 AM Thursday June 11 in Perth. + if !strings.Contains(body, "9:00 AM") { + t.Errorf("expected confirmation in Perth time ('9:00 AM'), got: %q", body) + } + if strings.Contains(body, "1:00 AM") { + t.Errorf("confirmation rendered the UTC instant ('1:00 AM') instead of meeting time: %q", body) + } + if attendeeTZ != "Australia/Perth" { + t.Errorf("attendee timeZone sent to Cal.com: got %q, want Australia/Perth", attendeeTZ) + } +} + +// TestCalcomBookingWidget_NoClientTimezonePlumbing pins the removal of +// client-side timezone detection — the mechanism behind the original bidbuddy +// incident (a hidden input's "UTC" default that never updated on cached +// pages). The widget must carry no timezone input, attribute, or detection +// call; the locale capture must still defer to DOMContentLoaded (the input it +// fills sits after the script in parse order); and the visible label must be +// the server-resolved zone. +func TestCalcomBookingWidget_NoClientTimezonePlumbing(t *testing.T) { + var buf strings.Builder + cfg := BookingConfig{ + BlockID: "b1", + Username: "alice", + EventTypeSlug: "30min", + WeeksToShow: 2, + WeekStart: "2026-06-11", + Today: "2026-06-11", + TimeZone: "Australia/Perth", + Dates: nil, + ShowTimezone: true, + } + if err := CalcomBookingWidget(cfg).Render(context.Background(), &buf); err != nil { + t.Fatalf("render widget: %v", err) + } + html := buf.String() + + for _, leftover := range []string{`name="timezone"`, "data-timezone", "Intl.DateTimeFormat"} { + if strings.Contains(html, leftover) { + t.Errorf("widget still ships client timezone plumbing (%s)", leftover) + } + } + if !strings.Contains(html, "DOMContentLoaded") { + t.Errorf("widget init script must defer until DOMContentLoaded — running during parse leaves the locale input at its empty default") + } + if !strings.Contains(html, "Australia/Perth") { + t.Errorf("ShowTimezone label must render the server-resolved zone, got: %q", html) + } +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..33c1187 --- /dev/null +++ b/types.go @@ -0,0 +1,140 @@ +package main + +// TimeSlot represents an available booking time slot +type TimeSlot struct { + Start string // ISO 8601 timestamp + End string // ISO 8601 timestamp + DisplayTime string // Human-readable time, e.g., "10:00 AM" + DateKey string // Date key from Cal.com API response +} + +// SlotConfig holds configuration for rendering time slots +type SlotConfig struct { + BlockID string + Username string + EventType string + SelectedDate string // YYYY-MM-DD format +} + +// FormConfig holds configuration for the booking form +type FormConfig struct { + BlockID string + Username string + EventType string + SelectedDate string // Human-readable date + SelectedTime string // Human-readable time + StartTime string // ISO 8601 timestamp for the booking + TimeZone string // IANA timezone of the event's availability schedule + BookingFields []BookingField + // DefaultCountryCode is the ISO alpha-2 region pre-selected in the phone + // country combobox, inferred from the visitor's timezone/locale. "" = none. + DefaultCountryCode string + // CaptchaEnabled renders the shared Cap proof-of-work widget in the confirm + // step. Server-authoritative: resolved from the block's stored captchaEnabled + // flag by blockId (HandleGetForm), the same source HandleCreateBooking + // enforces against — a bot cannot influence it. + CaptchaEnabled bool +} + +// BookingResult holds the result of a successful booking +type BookingResult struct { + Success bool + BookingID string // Cal.com booking UID + FormattedDateTime string // e.g., "Monday, January 15, 2025 at 10:00 AM" + Email string + MeetingLink string // Join URL from the booking's `location` (video events only) +} + +// BookingField is a single field configured on a Cal.com event type. +// Mirrors the Cal.com v2 event-type `bookingFields` array. +// +// Cal.com's v2 API (cal-api-version 2024-06-14) keys the field identifier as +// `slug`, NOT `name`, and returns an empty `label` for default/system fields +// (its own booker derives the visible label client-side via i18n). It also +// marks some system fields `hidden:true`. The raw decode therefore leaves +// Name/Label empty for the common case; GetEventType runs normalizeBookingFields +// to resolve Name (from Slug) and Label (from the system-field map) and to drop +// hidden fields before the form template ever sees them. +type BookingField struct { + Name string `json:"name"` // legacy/custom key; Slug is the v2 canonical identifier + Slug string `json:"slug"` + Label string `json:"label"` + Type string `json:"type"` // name|email|phone|text|textarea|number|boolean|select|multiselect|radio|checkbox + Required bool `json:"required"` + Hidden bool `json:"hidden"` + Placeholder string `json:"placeholder,omitempty"` + DefaultValue string `json:"defaultValue,omitempty"` + Options []BookingFieldOption `json:"options,omitempty"` + // Editable mirrors Cal.com's system/custom classification: "system", + // "system-but-optional", "system-but-hidden", "user", "user-readonly". + // Used to drop system-but-hidden fields before they reach the form. + Editable string `json:"editable,omitempty"` + // Views restricts a field to specific booker flows. rescheduleReason carries + // views:[{id:"reschedule"}] — i.e. it must render only when rescheduling, + // never on a new booking (see Cal.com getBookingFields.ts). + Views []BookingFieldView `json:"views,omitempty"` +} + +// BookingFieldOption is a single choice for a select/radio/checkbox field. +type BookingFieldOption struct { + Label string `json:"label"` + Value string `json:"value"` +} + +// BookingFieldView mirrors a Cal.com bookingField `views` entry. A non-empty +// views array scopes a field to particular booker flows (e.g. reschedule). +type BookingFieldView struct { + ID string `json:"id"` + Label string `json:"label,omitempty"` +} + +// EventTypeSummary is a compact projection used to populate the editor dropdown. +type EventTypeSummary struct { + ID int64 `json:"id"` + Slug string `json:"slug"` + Title string `json:"title"` + LengthInMinutes int `json:"lengthInMinutes"` +} + +// EventType is the full event-type record including its booking fields. +type EventType struct { + EventTypeSummary + BookingFields []BookingField `json:"bookingFields"` + // ScheduleID is the availability schedule backing this event type. nil + // means "the host's default schedule" — resolve via Schedule.IsDefault. + ScheduleID *int64 `json:"scheduleId"` +} + +// Schedule is a Cal.com availability schedule. Its TimeZone is the meeting's +// timezone: the zone the host configured their availability in, and the zone +// all booking times are displayed in (the visitor's browser zone is ignored +// by design — see eventLocation in handler.go). +type Schedule struct { + ID int64 `json:"id"` + Name string `json:"name"` + TimeZone string `json:"timeZone"` + IsDefault bool `json:"isDefault"` +} + +// WebhookPayload is the inner `payload` object of a Cal.com webhook event. +type WebhookPayload struct { + UID string `json:"uid"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + EventType struct { + Title string `json:"title"` + Slug string `json:"slug"` + } `json:"eventType"` + Attendees []struct { + Name string `json:"name"` + Email string `json:"email"` + TimeZone string `json:"timeZone"` + } `json:"attendees"` +} + +// WebhookEvent is the top-level Cal.com webhook envelope. +type WebhookEvent struct { + TriggerEvent string `json:"triggerEvent"` + CreatedAt string `json:"createdAt"` + Payload WebhookPayload `json:"payload"` +} diff --git a/tzdata.go b/tzdata.go new file mode 100644 index 0000000..2e7098e --- /dev/null +++ b/tzdata.go @@ -0,0 +1,7 @@ +package main + +// The production runtime image (alpine, no tzdata package) has no +// /usr/share/zoneinfo, and booking-time windowing/rendering resolves IANA +// zones via time.LoadLocation. Embed the zone database in the binary so +// timezone resolution never depends on the container's filesystem. +import _ "time/tzdata" diff --git a/web/dist/assets/__federation_expose_Editor-BDCHVRx7.js b/web/dist/assets/__federation_expose_Editor-BDCHVRx7.js new file mode 100644 index 0000000..d9ebc3f --- /dev/null +++ b/web/dist/assets/__federation_expose_Editor-BDCHVRx7.js @@ -0,0 +1,550 @@ +import { importShared } from "./__federation_fn_import-hlt2XzeI.js"; +import { j as jsxRuntimeExports } from "./jsx-runtime-CvJTHeKY.js"; + +const { useCallback, useEffect, useState } = await importShared("react"); + +const { + Alert, + AlertDescription, + Button, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + Tabs, + TabsContent, + TabsList, + TabsTrigger, + Input, + Label, + Textarea, + Switch, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + Settings, + Edit3, + CheckCircle2, + XCircle, + Loader2, + Key, + AlertCircle, + ExternalLink, + RefreshCw, + t, +} = await importShared("@block-ninja/ui"); + +function renderConnectedAccount(apiKeyConfigured, calcomUsername) { + if (apiKeyConfigured === false) { + return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { + className: "text-xs text-muted-foreground", + children: t("calcom.editor.connectedAs.noKey", "Configure the API key in plugin settings to connect an account."), + }); + } + if (calcomUsername) { + return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm font-mono", children: calcomUsername }); + } + return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("calcom.editor.connectedAs.resolving", "Resolving account from API key…") }); +} +function CalcomBlockEditor({ content, onChange }) { + const [activeTab, setActiveTab] = useState("config"); + const [apiKeyConfigured, setApiKeyConfigured] = useState(null); + const [calcomUsername, setCalcomUsername] = useState(""); + const [eventTypes, setEventTypes] = useState([]); + const [loadingEventTypes, setLoadingEventTypes] = useState(false); + const [fetchFailed, setFetchFailed] = useState(false); + const [retryCounter, setRetryCounter] = useState(0); + const handleFieldChange = useCallback( + (field, value) => { + onChange({ ...content, [field]: value }); + }, + [content, onChange], + ); + const getString = (field, defaultValue = "") => { + return content[field] || defaultValue; + }; + const getNumber = (field, defaultValue = 0) => { + const val = content[field]; + if (typeof val === "number") return val; + if (typeof val === "string") return parseInt(val, 10) || defaultValue; + return defaultValue; + }; + const getBool = (field, defaultValue = false) => { + const val = content[field]; + if (typeof val === "boolean") return val; + return defaultValue; + }; + const eventTypeSlug = getString("eventTypeSlug"); + useEffect(() => { + fetch(`/api/plugins/calcomblock/settings`) + .then((r) => r.json()) + .then((d) => { + setApiKeyConfigured(!!d.api_key_configured); + setCalcomUsername(d.username ?? ""); + }) + .catch(() => setApiKeyConfigured(false)); + }, []); + useEffect(() => { + if (calcomUsername && content.username !== calcomUsername) { + onChange({ ...content, username: calcomUsername }); + } + }, [calcomUsername, content, onChange]); + useEffect(() => { + if (!apiKeyConfigured) { + setEventTypes([]); + setFetchFailed(false); + return; + } + const timer = setTimeout(() => { + setLoadingEventTypes(true); + setFetchFailed(false); + fetch(`/api/plugins/calcomblock/event-types?username=${encodeURIComponent(calcomUsername)}`) + .then((r) => r.json()) + .then((d) => { + if (d.success) { + setEventTypes(d.event_types ?? []); + } else { + setEventTypes([]); + setFetchFailed(true); + } + }) + .catch(() => { + setEventTypes([]); + setFetchFailed(true); + }) + .finally(() => setLoadingEventTypes(false)); + }, 300); + return () => clearTimeout(timer); + }, [apiKeyConfigured, calcomUsername, retryCounter]); + const handleRetry = useCallback(() => { + setRetryCounter((n) => n + 1); + }, []); + const selectPlaceholder = (() => { + if (apiKeyConfigured === false) { + return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { + className: "inline-flex items-center gap-1.5 text-muted-foreground", + children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Key, { className: "h-3 w-3" }), t("calcom.editor.eventType.placeholder.noApiKey", "Configure API key first")], + }); + } + if (loadingEventTypes) { + return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { + className: "inline-flex items-center gap-1.5 text-muted-foreground", + children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-3 w-3 animate-spin" }), t("calcom.editor.eventType.placeholder.loading", "Loading…")], + }); + } + if (!calcomUsername) { + return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { + className: "inline-flex items-center gap-1.5 text-muted-foreground", + children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-3 w-3 animate-spin" }), t("calcom.editor.eventType.placeholder.resolvingUser", "Resolving account…")], + }); + } + if (eventTypes.length === 0) { + return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { + className: "inline-flex items-center gap-1.5 text-muted-foreground", + children: [/* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { className: "h-3 w-3" }), t("calcom.editor.eventType.placeholder.noEventTypes", "No event types found")], + }); + } + return t("calcom.editor.eventType.placeholder.select", "Select event type"); + })(); + const apiKeyReady = apiKeyConfigured === true; + const usernameReady = calcomUsername.length > 0; + const eventTypeReady = eventTypeSlug.length > 0; + const canOpenInCalcom = usernameReady && eventTypeReady; + const renderStatusRow = (ok, label, action) => + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "flex items-center gap-2 text-sm", + children: [ + ok ? + /* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { "className": "h-4 w-4 text-primary", "aria-hidden": "true" }) + : /* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { "className": "h-4 w-4 text-destructive", "aria-hidden": "true" }), + /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: ok ? "text-foreground" : "text-muted-foreground", children: label }), + action ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-auto", children: action }) : null, + ], + }); + return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { + className: "space-y-4", + children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { + value: activeTab, + onValueChange: setActiveTab, + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { + className: "grid w-full grid-cols-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsTrigger, { + value: "config", + className: "gap-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { className: "h-4 w-4" }), + /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("calcom.editor.tab.configuration", "Configuration") }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsTrigger, { + value: "content", + className: "gap-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Edit3, { className: "h-4 w-4" }), + /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("calcom.editor.tab.content", "Content") }), + ], + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsContent, { + value: "config", + className: "space-y-4 mt-4", + children: [ + apiKeyConfigured === false && + /* @__PURE__ */ jsxRuntimeExports.jsx(Alert, { + children: /* @__PURE__ */ jsxRuntimeExports.jsxs(AlertDescription, { + children: [ + t("calcom.editor.alert.apiKeyMissing.prefix", "Configure your Cal.com API key in"), + " ", + /* @__PURE__ */ jsxRuntimeExports.jsx("a", { + href: "/admin/plugins?plugin=calcomblock", + className: "text-primary hover:underline", + children: t("calcom.editor.alert.apiKeyMissing.linkLabel", "plugin settings"), + }), + " ", + t("calcom.editor.alert.apiKeyMissing.suffix", "to enable the event-type dropdown."), + ], + }), + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: t("calcom.editor.section.config.title", "Cal.com Settings") }), + /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { + children: t("calcom.editor.section.config.description", "Connect to your Cal.com account and select an event type"), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { + className: "space-y-4", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "space-y-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: t("calcom.editor.connectedAs.label", "Connected Account") }), + renderConnectedAccount(apiKeyConfigured, calcomUsername), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "space-y-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(Label, { + htmlFor: "eventTypeSlug", + children: [ + t("calcom.editor.eventType.label", "Event Type"), + " ", + /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive", children: "*" }), + ], + }), + fetchFailed ? + /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "flex items-center gap-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Input, { + id: "eventTypeSlug", + value: eventTypeSlug, + onChange: (e) => handleFieldChange("eventTypeSlug", e.target.value), + placeholder: t("calcom.editor.eventType.fallbackPlaceholder", "30min"), + className: "flex-1", + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { + "type": "button", + "size": "sm", + "variant": "outline", + "action": "retry", + "entity": "event-types", + "onClick": handleRetry, + "disabled": loadingEventTypes, + "aria-label": t("calcom.editor.eventType.retryAria", "Retry loading event types"), + "children": [ + /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { + "className": `h-4 w-4 ${loadingEventTypes ? "animate-spin" : ""}`, + "aria-hidden": "true", + }), + /* @__PURE__ */ jsxRuntimeExports.jsx("span", { + className: "ml-1", + children: t("calcom.editor.eventType.retryLabel", "Retry"), + }), + ], + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(Alert, { + variant: "destructive", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(AlertCircle, { "className": "h-4 w-4", "aria-hidden": "true" }), + /* @__PURE__ */ jsxRuntimeExports.jsx(AlertDescription, { + children: t("calcom.editor.eventType.fetchFailed", "Couldn't load event types from Cal.com. Enter the slug manually or retry."), + }), + ], + }), + ], + }) + : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { + value: eventTypeSlug, + onValueChange: (v) => handleFieldChange("eventTypeSlug", v), + disabled: loadingEventTypes || !apiKeyConfigured || eventTypes.length === 0, + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { + "aria-label": t("calcom.editor.eventType.label", "Event Type"), + "children": /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: selectPlaceholder }), + }), + /* @__PURE__ */ jsxRuntimeExports.jsx(SelectContent, { + children: eventTypes.map((et) => + /* @__PURE__ */ jsxRuntimeExports.jsxs( + SelectItem, + { + value: et.slug, + children: [ + et.title, + /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { + className: "text-muted-foreground", + children: [" ", "(", et.slug, " · ", et.lengthInMinutes, "m)"], + }), + ], + }, + et.id, + ), + ), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx("p", { + className: "text-xs text-muted-foreground", + children: t("calcom.editor.eventType.help", "Fetched from your Cal.com account"), + }), + ], + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "space-y-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "weeksToShow", children: t("calcom.editor.weeksToShow.label", "Weeks to Display") }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(Select, { + value: String(getNumber("weeksToShow", 2)), + onValueChange: (v) => handleFieldChange("weeksToShow", parseInt(v, 10)), + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(SelectTrigger, { + "aria-label": t("calcom.editor.weeksToShow.label", "Weeks to Display"), + "children": /* @__PURE__ */ jsxRuntimeExports.jsx(SelectValue, { placeholder: t("calcom.editor.weeksToShow.placeholder", "Select weeks") }), + }), + /* @__PURE__ */ jsxRuntimeExports.jsx(SelectContent, { + children: [1, 2, 3, 4, 5, 6, 7, 8].map((n) => + /* @__PURE__ */ jsxRuntimeExports.jsxs( + SelectItem, + { + value: String(n), + children: [ + n, + " ", + n > 1 ? t("calcom.editor.weeksToShow.unitPlural", "weeks") : t("calcom.editor.weeksToShow.unitSingular", "week"), + ], + }, + n, + ), + ), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx("p", { + className: "text-xs text-muted-foreground", + children: t("calcom.editor.weeksToShow.help", "How many weeks of dates to show in the calendar"), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "flex items-center justify-between rounded-lg border p-3", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "space-y-0.5", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { children: t("calcom.editor.showTimezone.label", "Show Timezone Selector") }), + /* @__PURE__ */ jsxRuntimeExports.jsx("p", { + className: "text-xs text-muted-foreground", + children: t("calcom.editor.showTimezone.help", "Show a label above the date grid indicating which timezone times are displayed in."), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx(Switch, { + "checked": getBool("showTimezone", true), + "onCheckedChange": (v) => handleFieldChange("showTimezone", v), + "aria-label": t("calcom.editor.showTimezone.label", "Show Timezone Selector"), + }), + ], + }), + ], + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: t("calcom.editor.status.title", "Configuration status") }), + /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { + children: t("calcom.editor.status.description", "Quick check of the values needed before this block renders bookings."), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { + className: "space-y-2", + children: [ + renderStatusRow( + apiKeyReady, + t("calcom.editor.status.apiKey", "API key configured"), + !apiKeyReady ? + /* @__PURE__ */ jsxRuntimeExports.jsx("a", { + href: "/admin/plugins?plugin=calcomblock", + className: "text-xs text-primary hover:underline", + children: t("calcom.editor.status.apiKey.configureLink", "Configure"), + }) + : null, + ), + renderStatusRow(usernameReady, t("calcom.editor.status.username", "Account resolved")), + renderStatusRow(eventTypeReady, t("calcom.editor.status.eventType", "Event type selected")), + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "flex items-center gap-2 text-sm", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-flex h-4 w-4 items-center justify-center text-muted-foreground", children: "·" }), + /* @__PURE__ */ jsxRuntimeExports.jsx("span", { + className: "text-muted-foreground", + children: t("calcom.editor.status.eventTypesAvailable", "{count} event types available").replace("{count}", String(eventTypes.length)), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx("div", { + className: "pt-2", + children: /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { + "type": "button", + "size": "sm", + "variant": "outline", + "action": "open", + "entity": "calcom-page", + "asChild": canOpenInCalcom, + "disabled": !canOpenInCalcom, + "aria-label": t("calcom.editor.status.openExternalAria", "Open Cal.com booking page in a new tab"), + "children": + canOpenInCalcom ? + /* @__PURE__ */ jsxRuntimeExports.jsxs("a", { + href: `https://cal.com/${calcomUsername}/${eventTypeSlug}`, + target: "_blank", + rel: "noopener noreferrer", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { "className": "h-4 w-4", "aria-hidden": "true" }), + /* @__PURE__ */ jsxRuntimeExports.jsx("span", { + className: "ml-1", + children: t("calcom.editor.status.openExternal", "Open in Cal.com"), + }), + ], + }) + : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { "className": "h-4 w-4", "aria-hidden": "true" }), + /* @__PURE__ */ jsxRuntimeExports.jsx("span", { + className: "ml-1", + children: t("calcom.editor.status.openExternal", "Open in Cal.com"), + }), + ], + }), + }), + }), + ], + }), + ], + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { + value: "content", + className: "space-y-4 mt-4", + children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: t("calcom.editor.section.content.title", "Widget Content") }), + /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { + children: t("calcom.editor.section.content.description", "Customize the text shown in the booking widget"), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { + className: "space-y-4", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "space-y-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "title", children: t("calcom.editor.title.label", "Title") }), + /* @__PURE__ */ jsxRuntimeExports.jsx(Input, { + id: "title", + value: getString("title"), + onChange: (e) => handleFieldChange("title", e.target.value), + placeholder: t("calcom.editor.title.placeholder", "Book a Meeting"), + }), + /* @__PURE__ */ jsxRuntimeExports.jsx("p", { + className: "text-xs text-muted-foreground", + children: t("calcom.editor.title.help", "Main heading shown above the calendar"), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "space-y-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "description", children: t("calcom.editor.description.label", "Description") }), + /* @__PURE__ */ jsxRuntimeExports.jsx(Textarea, { + id: "description", + value: getString("description"), + onChange: (e) => handleFieldChange("description", e.target.value), + placeholder: t("calcom.editor.description.placeholder", "Choose a convenient time for your consultation..."), + rows: 3, + }), + /* @__PURE__ */ jsxRuntimeExports.jsx("p", { + className: "text-xs text-muted-foreground", + children: t("calcom.editor.description.help", "Optional text shown below the title"), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "space-y-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { + htmlFor: "unavailableMessage", + children: t("calcom.editor.unavailableMessage.label", "Booking unavailable message"), + }), + /* @__PURE__ */ jsxRuntimeExports.jsx(Textarea, { + id: "unavailableMessage", + value: getString("unavailableMessage"), + onChange: (e) => handleFieldChange("unavailableMessage", e.target.value), + placeholder: t( + "calcom.editor.unavailableMessage.placeholder", + "Sorry — we couldn't complete your booking online. Please use our contact form and we'll get you booked in.", + ), + rows: 3, + }), + /* @__PURE__ */ jsxRuntimeExports.jsx("p", { + className: "text-xs text-muted-foreground", + children: t("calcom.editor.unavailableMessage.help", "Shown if an online booking can't be completed. Leave blank to use the default."), + }), + ], + }), + ], + }), + ], + }), + }), + ], + }), + }); +} + +export { CalcomBlockEditor as default }; diff --git a/web/dist/assets/__federation_expose_Settings-C7jFM1o4.js b/web/dist/assets/__federation_expose_Settings-C7jFM1o4.js new file mode 100644 index 0000000..1550240 --- /dev/null +++ b/web/dist/assets/__federation_expose_Settings-C7jFM1o4.js @@ -0,0 +1,619 @@ +import { importShared } from "./__federation_fn_import-hlt2XzeI.js"; +import { j as jsxRuntimeExports } from "./jsx-runtime-CvJTHeKY.js"; + +const { useState, useCallback, useEffect } = await importShared("react"); + +const { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + Input, + Label, + Button, + Alert, + AlertDescription, + Badge, + ScrollArea, + Loader2, + CheckCircle2, + XCircle, + AlertCircle, + Eye, + EyeOff, + Key, + Zap, + Settings, + Send, + Copy, + RefreshCw, + toast, + t, + useAlertDialog, +} = await importShared("@block-ninja/ui"); + +const copyToClipboard = async (text) => { + if (!text) return; + try { + await navigator.clipboard.writeText(text); + toast.success(t("calcom.settings.copied", "Copied to clipboard")); + } catch (_error) { + toast.error(t("calcom.settings.copyFailed", "Could not copy to clipboard")); + } +}; +const formatRelativeTime = (iso) => { + const parsed = Date.parse(iso); + if (Number.isNaN(parsed)) return iso; + const diffMs = Date.now() - parsed; + const minutes = Math.round(diffMs / 6e4); + if (minutes < 1) return t("calcom.settings.relative.justNow", "just now"); + if (minutes < 60) { + return minutes === 1 ? t("calcom.settings.relative.minute", "1 minute ago") : t("calcom.settings.relative.minutes", "{count} minutes ago").replace("{count}", String(minutes)); + } + const hours = Math.round(minutes / 60); + if (hours < 24) { + return hours === 1 ? t("calcom.settings.relative.hour", "1 hour ago") : t("calcom.settings.relative.hours", "{count} hours ago").replace("{count}", String(hours)); + } + const days = Math.round(hours / 24); + return days === 1 ? t("calcom.settings.relative.day", "1 day ago") : t("calcom.settings.relative.days", "{count} days ago").replace("{count}", String(days)); +}; +function CalcomSettings({ pluginName }) { + const { confirm } = useAlertDialog(); + const [apiKey, setApiKey] = useState(""); + const [showApiKey, setShowApiKey] = useState(false); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [testing, setTesting] = useState(false); + const [rotating, setRotating] = useState(false); + const [showSecret, setShowSecret] = useState(false); + const [settings, setSettings] = useState(null); + const [testResult, setTestResult] = useState(null); + const [saveMessage, setSaveMessage] = useState(null); + const baseUrl = `/api/plugins/${pluginName}`; + const loadSettings = useCallback(async () => { + try { + const res = await fetch(`${baseUrl}/settings`); + if (res.ok) { + const data = await res.json(); + setSettings(data); + } + } catch (_error) { + console.error("Failed to load settings"); + } finally { + setLoading(false); + } + }, [baseUrl]); + useEffect(() => { + loadSettings(); + }, [loadSettings]); + const handleSave = async () => { + if (!apiKey.trim()) { + setSaveMessage({ type: "error", text: t("calcom.settings.apiKey.errorEmpty", "Please enter an API key") }); + return; + } + setSaving(true); + setSaveMessage(null); + setTestResult(null); + try { + const res = await fetch(`${baseUrl}/settings`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ api_key: apiKey }), + }); + if (res.ok) { + await loadSettings(); + setApiKey(""); + setSaveMessage({ type: "success", text: t("calcom.settings.apiKey.saved", "API key saved successfully") }); + } else { + const text = await res.text(); + setSaveMessage({ type: "error", text: text || t("calcom.settings.apiKey.saveFailed", "Failed to save API key") }); + } + } catch (_error) { + setSaveMessage({ type: "error", text: t("calcom.settings.serverError", "Failed to connect to server") }); + } finally { + setSaving(false); + } + }; + const handleTest = async () => { + setTesting(true); + setTestResult(null); + try { + const res = await fetch(`${baseUrl}/test`, { method: "POST" }); + const data = await res.json(); + setTestResult(data); + } catch (_error) { + setTestResult({ success: false, error: t("calcom.settings.serverError", "Failed to connect to server") }); + } finally { + setTesting(false); + } + }; + const handleClear = async () => { + setSaving(true); + setSaveMessage(null); + try { + const res = await fetch(`${baseUrl}/settings`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ api_key: "" }), + }); + if (res.ok) { + await loadSettings(); + setTestResult(null); + setSaveMessage({ type: "success", text: t("calcom.settings.apiKey.removed", "API key removed") }); + } + } catch (_error) { + setSaveMessage({ type: "error", text: t("calcom.settings.apiKey.clearFailed", "Failed to clear API key") }); + } finally { + setSaving(false); + } + }; + const handleRotate = async () => { + const confirmed = await confirm({ + title: t("calcom.settings.webhook.rotateConfirmTitle", "Rotate webhook secret?"), + message: t("calcom.settings.webhook.rotateConfirmMessage", "This invalidates the existing secret immediately. You'll need to update Cal.com to keep webhooks flowing."), + confirmLabel: t("calcom.settings.webhook.rotateConfirmLabel", "Rotate"), + variant: "destructive", + }); + if (!confirmed) return; + setRotating(true); + setSaveMessage(null); + try { + const res = await fetch(`${baseUrl}/settings/rotate-webhook-secret`, { method: "POST" }); + if (res.ok) { + const data = await res.json(); + setSettings((prev) => (prev ? { ...prev, webhook_secret_masked: data.webhook_secret_masked, webhook_secret_configured: true } : prev)); + const successMsg = t("calcom.settings.webhook.rotated", "Webhook secret rotated. Update it in Cal.com."); + setSaveMessage({ type: "success", text: successMsg }); + toast.success(successMsg); + } else { + setSaveMessage({ type: "error", text: t("calcom.settings.webhook.rotateFailed", "Failed to rotate webhook secret") }); + } + } catch (_error) { + setSaveMessage({ type: "error", text: t("calcom.settings.serverError", "Failed to connect to server") }); + } finally { + setRotating(false); + } + }; + if (loading) { + return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { + className: "flex items-center justify-center py-8", + children: /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground" }), + }); + } + const apiKeyVisibilityLabel = showApiKey ? t("calcom.settings.toggleVisibility.hide", "Hide") : t("calcom.settings.toggleVisibility.show", "Show"); + const secretVisibilityLabel = showSecret ? t("calcom.settings.toggleVisibility.hide", "Hide") : t("calcom.settings.toggleVisibility.show", "Show"); + return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "space-y-6", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { + className: "text-xl font-semibold flex items-center gap-2", + children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { className: "h-5 w-5" }), t("calcom.settings.heading", "Cal.com Integration")], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx("p", { + className: "text-sm text-muted-foreground", + children: t("calcom.settings.headingDescription", "Configure your Cal.com API key to enable booking functionality"), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { + className: "text-base flex items-center gap-2", + children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Key, { className: "h-4 w-4" }), t("calcom.settings.apiKey.title", "API Key Status")], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { + children: t("calcom.settings.apiKey.description", "Your Cal.com API key is required for the booking widget to function"), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { + className: "space-y-4", + children: [ + settings?.api_key_configured ? + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "flex items-center justify-between rounded-lg border bg-muted/30 p-3", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "flex items-center gap-3", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { + variant: "default", + className: "gap-1.5", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "h-3.5 w-3.5" }), + t("calcom.settings.apiKey.configuredLabel", "Configured"), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx("p", { + className: "text-xs text-muted-foreground font-mono", + children: settings.api_key_masked || t("calcom.settings.apiKey.maskedFallback", "***configured***"), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { + action: "delete", + entity: "plugin-setting", + variant: "ghost", + size: "sm", + onClick: handleClear, + disabled: saving, + children: t("calcom.settings.apiKey.remove", "Remove"), + }), + ], + }) + : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "flex items-center gap-3 rounded-lg border bg-muted/30 p-3", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { + variant: "outline", + className: "gap-1.5", + children: [/* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { className: "h-3.5 w-3.5" }), t("calcom.settings.apiKey.notConfiguredLabel", "Not configured")], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx("p", { + className: "text-xs text-muted-foreground", + children: t("calcom.settings.apiKey.notConfiguredHelp", "Enter your Cal.com API key below to enable booking"), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "space-y-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { + htmlFor: "apiKey", + children: settings?.api_key_configured ? t("calcom.settings.apiKey.updateLabel", "Update API Key") : t("calcom.settings.apiKey.label", "API Key"), + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "flex gap-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "relative flex-1", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Input, { + id: "apiKey", + type: showApiKey ? "text" : "password", + value: apiKey, + onChange: (e) => setApiKey(e.target.value), + placeholder: t("calcom.settings.apiKey.placeholder", "cal_live_..."), + className: "pr-10", + }), + /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { + "type": "button", + "variant": "ghost", + "action": "toggle", + "entity": "api-key-visibility", + "onClick": () => setShowApiKey(!showApiKey), + "aria-label": apiKeyVisibilityLabel, + "className": "absolute right-3 top-1/2 -translate-y-1/2 h-auto p-0 text-muted-foreground hover:bg-transparent hover:text-foreground", + "children": + showApiKey ? + /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "h-4 w-4" }) + : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "h-4 w-4" }), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { + action: "save", + entity: "plugin-setting", + onClick: handleSave, + disabled: saving || !apiKey.trim(), + children: saving ? /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : t("calcom.settings.apiKey.save", "Save"), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { + className: "text-xs text-muted-foreground", + children: [ + t("calcom.settings.apiKey.helpPrefix", "Get your API key from"), + " ", + /* @__PURE__ */ jsxRuntimeExports.jsx("a", { + href: "https://app.cal.com/settings/developer/api-keys", + target: "_blank", + rel: "noopener noreferrer", + className: "text-primary hover:underline", + children: t("calcom.settings.apiKey.helpLink", "Cal.com API Settings"), + }), + ], + }), + ], + }), + saveMessage && + /* @__PURE__ */ jsxRuntimeExports.jsx(Alert, { + variant: saveMessage.type === "error" ? "destructive" : "default", + children: /* @__PURE__ */ jsxRuntimeExports.jsx(AlertDescription, { children: saveMessage.text }), + }), + ], + }), + ], + }), + settings?.api_key_configured && + /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { + className: "text-base flex items-center gap-2", + children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "h-4 w-4" }), t("calcom.settings.test.title", "Test Connection")], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: t("calcom.settings.test.description", "Verify your API key works and see available event types") }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { + className: "space-y-4", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { + action: "settings", + entity: "plugin-connection", + onClick: handleTest, + disabled: testing, + children: + testing ? + /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { + children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), t("calcom.settings.test.testing", "Testing...")], + }) + : t("calcom.settings.test.button", "Test Connection"), + }), + testResult && + /* @__PURE__ */ jsxRuntimeExports.jsx("div", { + className: "space-y-3", + children: + testResult.success ? + /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(Alert, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "h-4 w-4 text-success" }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(AlertDescription, { + className: "ml-2", + children: [ + t("calcom.settings.test.successPrefix", "Connection successful! Found"), + " ", + testResult.event_types?.length || 0, + " ", + t("calcom.settings.test.successSuffix", "event types."), + ], + }), + ], + }), + testResult.event_types && + testResult.event_types.length > 0 && + /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { + className: "max-h-96 rounded-lg border", + children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { + className: "w-full text-sm", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { + className: "bg-muted", + children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx("th", { + className: "text-left px-3 py-2 font-medium", + children: t("calcom.settings.test.tableEventType", "Event Type"), + }), + /* @__PURE__ */ jsxRuntimeExports.jsx("th", { + className: "text-left px-3 py-2 font-medium", + children: t("calcom.settings.test.tableSlug", "Slug"), + }), + ], + }), + }), + /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { + children: testResult.event_types.map((et) => + /* @__PURE__ */ jsxRuntimeExports.jsxs( + "tr", + { + className: "border-t", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-2", children: et.title }), + /* @__PURE__ */ jsxRuntimeExports.jsx("td", { + className: "px-3 py-2 font-mono text-xs", + children: et.slug, + }), + ], + }, + et.id, + ), + ), + }), + ], + }), + }), + ], + }) + : /* @__PURE__ */ jsxRuntimeExports.jsxs(Alert, { + variant: "destructive", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(XCircle, { className: "h-4 w-4" }), + /* @__PURE__ */ jsxRuntimeExports.jsx(AlertDescription, { + className: "ml-2", + children: testResult.error || t("calcom.settings.test.failure", "Connection failed"), + }), + ], + }), + }), + ], + }), + ], + }), + settings?.api_key_configured && + /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, { + className: "text-base flex items-center gap-2", + children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Send, { className: "h-4 w-4" }), t("calcom.settings.webhook.title", "Webhook")], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { + children: t("calcom.settings.webhook.description", "Configure this URL in Cal.com → Settings → Developer → Webhooks to receive booking events."), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { + className: "space-y-4", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "space-y-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "webhookUrl", children: t("calcom.settings.webhook.urlLabel", "Webhook URL") }), + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "flex gap-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Input, { + id: "webhookUrl", + readOnly: true, + value: settings?.webhook_url || "", + className: "flex-1 font-mono text-xs", + }), + /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { + "action": "copy", + "entity": "webhook-url", + "variant": "outline", + "size": "sm", + "aria-label": t("calcom.settings.webhook.copyUrl", "Copy webhook URL"), + "onClick": () => copyToClipboard(settings?.webhook_url || ""), + "children": /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "h-4 w-4" }), + }), + ], + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "space-y-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Label, { htmlFor: "webhookSecret", children: t("calcom.settings.webhook.secretLabel", "Webhook Secret") }), + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "flex gap-2", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "relative flex-1", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(Input, { + id: "webhookSecret", + type: showSecret ? "text" : "password", + readOnly: true, + value: settings?.webhook_secret_masked || t("calcom.settings.webhook.secretNotConfigured", "Not configured"), + className: "pr-10 font-mono text-xs", + }), + /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { + "type": "button", + "variant": "ghost", + "action": "toggle", + "entity": "webhook-secret-visibility", + "onClick": () => setShowSecret(!showSecret), + "aria-label": secretVisibilityLabel, + "className": "absolute right-3 top-1/2 -translate-y-1/2 h-auto p-0 text-muted-foreground hover:bg-transparent hover:text-foreground", + "children": + showSecret ? + /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { className: "h-4 w-4" }) + : /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { className: "h-4 w-4" }), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { + "action": "copy", + "entity": "webhook-secret", + "variant": "outline", + "size": "sm", + "aria-label": t("calcom.settings.webhook.copySecret", "Copy webhook secret"), + "onClick": () => copyToClipboard(settings?.webhook_secret_masked || ""), + "children": /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "h-4 w-4" }), + }), + /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { + "action": "rotate", + "entity": "webhook-secret", + "variant": "outline", + "size": "sm", + "aria-label": t("calcom.settings.webhook.rotate", "Rotate webhook secret"), + "onClick": handleRotate, + "disabled": rotating, + "children": + rotating ? + /* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-4 w-4 animate-spin" }) + : /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "h-4 w-4" }), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx("p", { + className: "text-xs text-muted-foreground", + children: t( + "calcom.settings.webhook.secretHelp", + "Cal.com displays the secret only once. Regenerating invalidates the previous value immediately — update it in Cal.com after rotating.", + ), + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsx("div", { + className: "text-sm", + children: + settings?.last_event_at ? + /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "flex items-center gap-2 text-foreground", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(CheckCircle2, { className: "h-4 w-4 text-success" }), + /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { + children: [ + t("calcom.settings.webhook.lastEventPrefix", "Last event"), + " ", + formatRelativeTime(settings.last_event_at), + settings.last_event_type && + /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { + children: [ + " — ", + /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "font-mono text-xs", children: settings.last_event_type }), + ], + }), + ], + }), + ], + }) + : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { + className: "flex items-center gap-2 text-muted-foreground", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(AlertCircle, { className: "h-4 w-4" }), + /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("calcom.settings.webhook.waitingForEvent", "Status: Waiting for first event") }), + ], + }), + }), + ], + }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-base", children: t("calcom.settings.docs.title", "Documentation") }), + /* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: t("calcom.settings.docs.description", "Reference material for working with the Cal.com plugin.") }), + ], + }), + /* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { + className: "space-y-2 text-sm", + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx("a", { + href: "https://cal.com/docs/api-reference/v2", + target: "_blank", + rel: "noopener noreferrer", + className: "text-primary hover:underline block", + children: t("calcom.settings.docs.calcomApi", "Cal.com API documentation"), + }), + /* @__PURE__ */ jsxRuntimeExports.jsx("a", { + href: "/admin/docs/calcom-plugin", + target: "_blank", + rel: "noopener noreferrer", + className: "text-primary hover:underline block", + children: t("calcom.settings.docs.pluginGuide", "BlockNinja plugin guide"), + }), + ], + }), + ], + }), + ], + }); +} + +export { CalcomSettings as default }; diff --git a/web/dist/assets/__federation_fn_import-hlt2XzeI.js b/web/dist/assets/__federation_fn_import-hlt2XzeI.js new file mode 100644 index 0000000..e7b42c9 --- /dev/null +++ b/web/dist/assets/__federation_fn_import-hlt2XzeI.js @@ -0,0 +1,392 @@ +const buildIdentifier = "[0-9A-Za-z-]+"; +const build = `(?:\\+(${buildIdentifier}(?:\\.${buildIdentifier})*))`; +const numericIdentifier = "0|[1-9]\\d*"; +const numericIdentifierLoose = "[0-9]+"; +const nonNumericIdentifier = "\\d*[a-zA-Z-][a-zA-Z0-9-]*"; +const preReleaseIdentifierLoose = `(?:${numericIdentifierLoose}|${nonNumericIdentifier})`; +const preReleaseLoose = `(?:-?(${preReleaseIdentifierLoose}(?:\\.${preReleaseIdentifierLoose})*))`; +const preReleaseIdentifier = `(?:${numericIdentifier}|${nonNumericIdentifier})`; +const preRelease = `(?:-(${preReleaseIdentifier}(?:\\.${preReleaseIdentifier})*))`; +const xRangeIdentifier = `${numericIdentifier}|x|X|\\*`; +const xRangePlain = `[v=\\s]*(${xRangeIdentifier})(?:\\.(${xRangeIdentifier})(?:\\.(${xRangeIdentifier})(?:${preRelease})?${build}?)?)?`; +const hyphenRange = `^\\s*(${xRangePlain})\\s+-\\s+(${xRangePlain})\\s*$`; +const mainVersionLoose = `(${numericIdentifierLoose})\\.(${numericIdentifierLoose})\\.(${numericIdentifierLoose})`; +const loosePlain = `[v=\\s]*${mainVersionLoose}${preReleaseLoose}?${build}?`; +const gtlt = "((?:<|>)?=?)"; +const comparatorTrim = `(\\s*)${gtlt}\\s*(${loosePlain}|${xRangePlain})`; +const loneTilde = "(?:~>?)"; +const tildeTrim = `(\\s*)${loneTilde}\\s+`; +const loneCaret = "(?:\\^)"; +const caretTrim = `(\\s*)${loneCaret}\\s+`; +const star = "(<|>)?=?\\s*\\*"; +const caret = `^${loneCaret}${xRangePlain}$`; +const mainVersion = `(${numericIdentifier})\\.(${numericIdentifier})\\.(${numericIdentifier})`; +const fullPlain = `v?${mainVersion}${preRelease}?${build}?`; +const tilde = `^${loneTilde}${xRangePlain}$`; +const xRange = `^${gtlt}\\s*${xRangePlain}$`; +const comparator = `^${gtlt}\\s*(${fullPlain})$|^$`; +const gte0 = "^\\s*>=\\s*0.0.0\\s*$"; +function parseRegex(source) { + return new RegExp(source); +} +function isXVersion(version) { + return !version || version.toLowerCase() === "x" || version === "*"; +} +function pipe(...fns) { + return (x) => { + return fns.reduce((v, f) => f(v), x); + }; +} +function extractComparator(comparatorString) { + return comparatorString.match(parseRegex(comparator)); +} +function combineVersion(major, minor, patch, preRelease2) { + const mainVersion2 = `${major}.${minor}.${patch}`; + if (preRelease2) { + return `${mainVersion2}-${preRelease2}`; + } + return mainVersion2; +} +function parseHyphen(range) { + return range.replace(parseRegex(hyphenRange), (_range, from, fromMajor, fromMinor, fromPatch, _fromPreRelease, _fromBuild, to, toMajor, toMinor, toPatch, toPreRelease) => { + if (isXVersion(fromMajor)) { + from = ""; + } else if (isXVersion(fromMinor)) { + from = `>=${fromMajor}.0.0`; + } else if (isXVersion(fromPatch)) { + from = `>=${fromMajor}.${fromMinor}.0`; + } else { + from = `>=${from}`; + } + if (isXVersion(toMajor)) { + to = ""; + } else if (isXVersion(toMinor)) { + to = `<${+toMajor + 1}.0.0-0`; + } else if (isXVersion(toPatch)) { + to = `<${toMajor}.${+toMinor + 1}.0-0`; + } else if (toPreRelease) { + to = `<=${toMajor}.${toMinor}.${toPatch}-${toPreRelease}`; + } else { + to = `<=${to}`; + } + return `${from} ${to}`.trim(); + }); +} +function parseComparatorTrim(range) { + return range.replace(parseRegex(comparatorTrim), "$1$2$3"); +} +function parseTildeTrim(range) { + return range.replace(parseRegex(tildeTrim), "$1~"); +} +function parseCaretTrim(range) { + return range.replace(parseRegex(caretTrim), "$1^"); +} +function parseCarets(range) { + return range + .trim() + .split(/\s+/) + .map((rangeVersion) => { + return rangeVersion.replace(parseRegex(caret), (_, major, minor, patch, preRelease2) => { + if (isXVersion(major)) { + return ""; + } else if (isXVersion(minor)) { + return `>=${major}.0.0 <${+major + 1}.0.0-0`; + } else if (isXVersion(patch)) { + if (major === "0") { + return `>=${major}.${minor}.0 <${major}.${+minor + 1}.0-0`; + } else { + return `>=${major}.${minor}.0 <${+major + 1}.0.0-0`; + } + } else if (preRelease2) { + if (major === "0") { + if (minor === "0") { + return `>=${major}.${minor}.${patch}-${preRelease2} <${major}.${minor}.${+patch + 1}-0`; + } else { + return `>=${major}.${minor}.${patch}-${preRelease2} <${major}.${+minor + 1}.0-0`; + } + } else { + return `>=${major}.${minor}.${patch}-${preRelease2} <${+major + 1}.0.0-0`; + } + } else { + if (major === "0") { + if (minor === "0") { + return `>=${major}.${minor}.${patch} <${major}.${minor}.${+patch + 1}-0`; + } else { + return `>=${major}.${minor}.${patch} <${major}.${+minor + 1}.0-0`; + } + } + return `>=${major}.${minor}.${patch} <${+major + 1}.0.0-0`; + } + }); + }) + .join(" "); +} +function parseTildes(range) { + return range + .trim() + .split(/\s+/) + .map((rangeVersion) => { + return rangeVersion.replace(parseRegex(tilde), (_, major, minor, patch, preRelease2) => { + if (isXVersion(major)) { + return ""; + } else if (isXVersion(minor)) { + return `>=${major}.0.0 <${+major + 1}.0.0-0`; + } else if (isXVersion(patch)) { + return `>=${major}.${minor}.0 <${major}.${+minor + 1}.0-0`; + } else if (preRelease2) { + return `>=${major}.${minor}.${patch}-${preRelease2} <${major}.${+minor + 1}.0-0`; + } + return `>=${major}.${minor}.${patch} <${major}.${+minor + 1}.0-0`; + }); + }) + .join(" "); +} +function parseXRanges(range) { + return range + .split(/\s+/) + .map((rangeVersion) => { + return rangeVersion.trim().replace(parseRegex(xRange), (ret, gtlt2, major, minor, patch, preRelease2) => { + const isXMajor = isXVersion(major); + const isXMinor = isXMajor || isXVersion(minor); + const isXPatch = isXMinor || isXVersion(patch); + if (gtlt2 === "=" && isXPatch) { + gtlt2 = ""; + } + preRelease2 = ""; + if (isXMajor) { + if (gtlt2 === ">" || gtlt2 === "<") { + return "<0.0.0-0"; + } else { + return "*"; + } + } else if (gtlt2 && isXPatch) { + if (isXMinor) { + minor = 0; + } + patch = 0; + if (gtlt2 === ">") { + gtlt2 = ">="; + if (isXMinor) { + major = +major + 1; + minor = 0; + patch = 0; + } else { + minor = +minor + 1; + patch = 0; + } + } else if (gtlt2 === "<=") { + gtlt2 = "<"; + if (isXMinor) { + major = +major + 1; + } else { + minor = +minor + 1; + } + } + if (gtlt2 === "<") { + preRelease2 = "-0"; + } + return `${gtlt2 + major}.${minor}.${patch}${preRelease2}`; + } else if (isXMinor) { + return `>=${major}.0.0${preRelease2} <${+major + 1}.0.0-0`; + } else if (isXPatch) { + return `>=${major}.${minor}.0${preRelease2} <${major}.${+minor + 1}.0-0`; + } + return ret; + }); + }) + .join(" "); +} +function parseStar(range) { + return range.trim().replace(parseRegex(star), ""); +} +function parseGTE0(comparatorString) { + return comparatorString.trim().replace(parseRegex(gte0), ""); +} +function compareAtom(rangeAtom, versionAtom) { + rangeAtom = +rangeAtom || rangeAtom; + versionAtom = +versionAtom || versionAtom; + if (rangeAtom > versionAtom) { + return 1; + } + if (rangeAtom === versionAtom) { + return 0; + } + return -1; +} +function comparePreRelease(rangeAtom, versionAtom) { + const { preRelease: rangePreRelease } = rangeAtom; + const { preRelease: versionPreRelease } = versionAtom; + if (rangePreRelease === void 0 && !!versionPreRelease) { + return 1; + } + if (!!rangePreRelease && versionPreRelease === void 0) { + return -1; + } + if (rangePreRelease === void 0 && versionPreRelease === void 0) { + return 0; + } + for (let i = 0, n = rangePreRelease.length; i <= n; i++) { + const rangeElement = rangePreRelease[i]; + const versionElement = versionPreRelease[i]; + if (rangeElement === versionElement) { + continue; + } + if (rangeElement === void 0 && versionElement === void 0) { + return 0; + } + if (!rangeElement) { + return 1; + } + if (!versionElement) { + return -1; + } + return compareAtom(rangeElement, versionElement); + } + return 0; +} +function compareVersion(rangeAtom, versionAtom) { + return ( + compareAtom(rangeAtom.major, versionAtom.major) || + compareAtom(rangeAtom.minor, versionAtom.minor) || + compareAtom(rangeAtom.patch, versionAtom.patch) || + comparePreRelease(rangeAtom, versionAtom) + ); +} +function eq(rangeAtom, versionAtom) { + return rangeAtom.version === versionAtom.version; +} +function compare(rangeAtom, versionAtom) { + switch (rangeAtom.operator) { + case "": + case "=": + return eq(rangeAtom, versionAtom); + case ">": + return compareVersion(rangeAtom, versionAtom) < 0; + case ">=": + return eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) < 0; + case "<": + return compareVersion(rangeAtom, versionAtom) > 0; + case "<=": + return eq(rangeAtom, versionAtom) || compareVersion(rangeAtom, versionAtom) > 0; + case void 0: { + return true; + } + default: + return false; + } +} +function parseComparatorString(range) { + return pipe(parseCarets, parseTildes, parseXRanges, parseStar)(range); +} +function parseRange(range) { + return pipe(parseHyphen, parseComparatorTrim, parseTildeTrim, parseCaretTrim)(range.trim()).split(/\s+/).join(" "); +} +function satisfy(version, range) { + if (!version) { + return false; + } + const parsedRange = parseRange(range); + const parsedComparator = parsedRange + .split(" ") + .map((rangeVersion) => parseComparatorString(rangeVersion)) + .join(" "); + const comparators = parsedComparator.split(/\s+/).map((comparator2) => parseGTE0(comparator2)); + const extractedVersion = extractComparator(version); + if (!extractedVersion) { + return false; + } + const [, versionOperator, , versionMajor, versionMinor, versionPatch, versionPreRelease] = extractedVersion; + const versionAtom = { + version: combineVersion(versionMajor, versionMinor, versionPatch, versionPreRelease), + major: versionMajor, + minor: versionMinor, + patch: versionPatch, + preRelease: versionPreRelease == null ? void 0 : versionPreRelease.split("."), + }; + for (const comparator2 of comparators) { + const extractedComparator = extractComparator(comparator2); + if (!extractedComparator) { + return false; + } + const [, rangeOperator, , rangeMajor, rangeMinor, rangePatch, rangePreRelease] = extractedComparator; + const rangeAtom = { + operator: rangeOperator, + version: combineVersion(rangeMajor, rangeMinor, rangePatch, rangePreRelease), + major: rangeMajor, + minor: rangeMinor, + patch: rangePatch, + preRelease: rangePreRelease == null ? void 0 : rangePreRelease.split("."), + }; + if (!compare(rangeAtom, versionAtom)) { + return false; + } + } + return true; +} + +const currentImports = {}; + +// eslint-disable-next-line no-undef +const moduleMap = { + "react": { get: () => () => __federation_import(new URL("__federation_shared_react-DoKb58Ht.js", import.meta.url).href), import: true }, + "react-dom": { get: () => () => __federation_import(new URL("__federation_shared_react-dom-DU2-P0kt.js", import.meta.url).href), import: true }, + "@block-ninja/ui": { get: () => () => __federation_import(new URL("__federation_shared_@block-ninja/ui-C3CAr7wz.js", import.meta.url).href), import: true }, +}; +const moduleCache = Object.create(null); +async function importShared(name, shareScope = "default") { + return moduleCache[name] ? new Promise((r) => r(moduleCache[name])) : (await getSharedFromRuntime(name, shareScope)) || getSharedFromLocal(name); +} +// eslint-disable-next-line +async function __federation_import(name) { + currentImports[name] ??= import(name); + return currentImports[name]; +} +async function getSharedFromRuntime(name, shareScope) { + let module = null; + if (globalThis?.__federation_shared__?.[shareScope]?.[name]) { + const versionObj = globalThis.__federation_shared__[shareScope][name]; + const requiredVersion = moduleMap[name]?.requiredVersion; + const hasRequiredVersion = !!requiredVersion; + if (hasRequiredVersion) { + const versionKey = Object.keys(versionObj).find((version) => satisfy(version, requiredVersion)); + if (versionKey) { + const versionValue = versionObj[versionKey]; + module = await (await versionValue.get())(); + } else { + console.log(`provider support ${name}(${versionKey}) is not satisfied requiredVersion(\${moduleMap[name].requiredVersion})`); + } + } else { + const versionKey = Object.keys(versionObj)[0]; + const versionValue = versionObj[versionKey]; + module = await (await versionValue.get())(); + } + } + if (module) { + return flattenModule(module, name); + } +} +async function getSharedFromLocal(name) { + if (moduleMap[name]?.import) { + let module = await (await moduleMap[name].get())(); + return flattenModule(module, name); + } else { + console.error(`consumer config import=false,so cant use callback shared module`); + } +} +function flattenModule(module, name) { + // use a shared module which export default a function will getting error 'TypeError: xxx is not a function' + if (typeof module.default === "function") { + Object.keys(module).forEach((key) => { + if (key !== "default") { + module.default[key] = module[key]; + } + }); + moduleCache[name] = module.default; + return module.default; + } + if (module.default) module = Object.assign({}, module.default, module); + moduleCache[name] = module; + return module; +} + +export { importShared, getSharedFromLocal as importSharedLocal, getSharedFromRuntime as importSharedRuntime }; diff --git a/web/dist/assets/__federation_shared_@block-ninja/ui-C3CAr7wz.js b/web/dist/assets/__federation_shared_@block-ninja/ui-C3CAr7wz.js new file mode 100644 index 0000000..d926ef2 --- /dev/null +++ b/web/dist/assets/__federation_shared_@block-ninja/ui-C3CAr7wz.js @@ -0,0 +1,36447 @@ +import { + u as useComposedRefs, + a as useDirection, + P as Primitive, + b as Presence, + c as composeEventHandlers, + d as createContextScope, + e as useCallbackRef, + f as useLayoutEffect2, + g as clamp, + h as Portal, + i as useDismissableLayerSurface, + R as ReactRemoveScroll, + j as hideOthers, + k as createSlot, + l as useFocusGuards, + F as FocusScope, + D as DismissableLayer, + m as useControllableState, + n as useId, + o as usePrevious$1, + p as useSize, + s as svg, + q as html, + w as whitespace, + r as ok, + V as VFileMessage, + t as find, + v as stringify, + x as stringify$1, + y as pointStart, + z as unified, + A as remarkParse, + B as remarkRehype, + C as VFile, + E as unreachable, + G as visit, + L as List, + T as Trigger, + H as Content, + S as Slot$1, + I as Root$2, + J as SelectTrigger$1, + K as SelectIcon, + M as SelectScrollUpButton$1, + N as SelectScrollDownButton$1, + O as SelectPortal, + Q as SelectContent$1, + U as SelectViewport, + W as SelectLabel$1, + X as SelectItem$1, + Y as SelectItemIndicator, + Z as SelectItemText, + _ as SelectSeparator$1, + $ as __vitePreload, + a0 as twMerge, + a1 as clsx, + a2 as cva, + a3 as Select$1, + a4 as SelectGroup$1, + a5 as SelectValue$1, + a6 as Root2$1, +} from "../index-BSkdT-DR.js"; +export { a7 as conservativeSchema, a8 as courseContentSchema, a9 as defaultSchema } from "../index-BSkdT-DR.js"; +import { importShared } from "../__federation_fn_import-hlt2XzeI.js"; +import { j as jsxRuntimeExports } from "../jsx-runtime-CvJTHeKY.js"; +import { g as getDefaultExportFromCjs } from "../_commonjsHelpers-B85MJLTf.js"; +import { c as createLucideIcon, s as shimExports, L as List$1 } from "../index-Coq9nAfE.js"; + +/** + * Special cases for React (`Record`). + * + * `hast` is close to `React` but differs in a couple of cases. + * To get a React property from a hast property, + * check if it is in `hastToReact`. + * If it is, use the corresponding value; + * otherwise, use the hast property. + * + * @type {Record} + */ +const hastToReact = { + classId: "classID", + dataType: "datatype", + itemId: "itemID", + strokeDashArray: "strokeDasharray", + strokeDashOffset: "strokeDashoffset", + strokeLineCap: "strokeLinecap", + strokeLineJoin: "strokeLinejoin", + strokeMiterLimit: "strokeMiterlimit", + typeOf: "typeof", + xLinkActuate: "xlinkActuate", + xLinkArcRole: "xlinkArcrole", + xLinkHref: "xlinkHref", + xLinkRole: "xlinkRole", + xLinkShow: "xlinkShow", + xLinkTitle: "xlinkTitle", + xLinkType: "xlinkType", + xmlnsXLink: "xmlnsXlink", +}; + +// src/scroll-area.tsx +const React2$1 = await importShared("react"); + +// src/use-state-machine.ts +const React$g = await importShared("react"); + +function useStateMachine(initialState, machine) { + return React$g.useReducer((state, event) => { + const nextState = machine[state][event]; + return nextState ?? state; + }, initialState); +} +var SCROLL_AREA_NAME = "ScrollArea"; +var [createScrollAreaContext] = createContextScope(SCROLL_AREA_NAME); +var [ScrollAreaProvider, useScrollAreaContext] = createScrollAreaContext(SCROLL_AREA_NAME); +var ScrollArea$1 = React2$1.forwardRef((props, forwardedRef) => { + const { __scopeScrollArea, type = "hover", dir, scrollHideDelay = 600, ...scrollAreaProps } = props; + const [scrollArea, setScrollArea] = React2$1.useState(null); + const [viewport, setViewport] = React2$1.useState(null); + const [content, setContent] = React2$1.useState(null); + const [scrollbarX, setScrollbarX] = React2$1.useState(null); + const [scrollbarY, setScrollbarY] = React2$1.useState(null); + const [cornerWidth, setCornerWidth] = React2$1.useState(0); + const [cornerHeight, setCornerHeight] = React2$1.useState(0); + const [scrollbarXEnabled, setScrollbarXEnabled] = React2$1.useState(false); + const [scrollbarYEnabled, setScrollbarYEnabled] = React2$1.useState(false); + const composedRefs = useComposedRefs(forwardedRef, setScrollArea); + const direction = useDirection(dir); + return /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaProvider, { + scope: __scopeScrollArea, + type, + dir: direction, + scrollHideDelay, + scrollArea, + viewport, + onViewportChange: setViewport, + content, + onContentChange: setContent, + scrollbarX, + onScrollbarXChange: setScrollbarX, + scrollbarXEnabled, + onScrollbarXEnabledChange: setScrollbarXEnabled, + scrollbarY, + onScrollbarYChange: setScrollbarY, + scrollbarYEnabled, + onScrollbarYEnabledChange: setScrollbarYEnabled, + onCornerWidthChange: setCornerWidth, + onCornerHeightChange: setCornerHeight, + children: /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.div, { + dir: direction, + ...scrollAreaProps, + ref: composedRefs, + style: { + "position": "relative", + // Pass corner sizes as CSS vars to reduce re-renders of context consumers + "--radix-scroll-area-corner-width": cornerWidth + "px", + "--radix-scroll-area-corner-height": cornerHeight + "px", + ...props.style, + }, + }), + }); +}); +ScrollArea$1.displayName = SCROLL_AREA_NAME; +var VIEWPORT_NAME = "ScrollAreaViewport"; +var ScrollAreaViewport = React2$1.forwardRef((props, forwardedRef) => { + const { __scopeScrollArea, children, nonce, ...viewportProps } = props; + const context = useScrollAreaContext(VIEWPORT_NAME, __scopeScrollArea); + const ref = React2$1.useRef(null); + const composedRefs = useComposedRefs(forwardedRef, ref, context.onViewportChange); + return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaViewportStyle, { nonce }), + /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.div, { + "data-radix-scroll-area-viewport": "", + ...viewportProps, + "ref": composedRefs, + "style": { + /** + * We don't support `visible` because the intention is to have at least one scrollbar + * if this component is used and `visible` will behave like `auto` in that case + * https://developer.mozilla.org/en-US/docs/Web/CSS/overflow#description + * + * We don't handle `auto` because the intention is for the native implementation + * to be hidden if using this component. We just want to ensure the node is scrollable + * so could have used either `scroll` or `auto` here. We picked `scroll` to prevent + * the browser from having to work out whether to render native scrollbars or not, + * we tell it to with the intention of hiding them in CSS. + */ + overflowX: context.scrollbarXEnabled ? "scroll" : "hidden", + overflowY: context.scrollbarYEnabled ? "scroll" : "hidden", + ...props.style, + }, + "children": /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: context.onContentChange, style: { minWidth: "100%", display: "table" }, children }), + }), + ], + }); +}); +ScrollAreaViewport.displayName = VIEWPORT_NAME; +var ScrollAreaViewportStyle = React2$1.memo( + ({ nonce }) => { + return /* @__PURE__ */ jsxRuntimeExports.jsx("style", { + dangerouslySetInnerHTML: { + __html: `[data-radix-scroll-area-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-scroll-area-viewport]::-webkit-scrollbar{display:none}`, + }, + nonce, + }); + }, + (prevProps, nextProps) => prevProps.nonce === nextProps.nonce, +); +var SCROLLBAR_NAME = "ScrollAreaScrollbar"; +var ScrollAreaScrollbar = React2$1.forwardRef((props, forwardedRef) => { + const { forceMount, ...scrollbarProps } = props; + const context = useScrollAreaContext(SCROLLBAR_NAME, props.__scopeScrollArea); + const { onScrollbarXEnabledChange, onScrollbarYEnabledChange } = context; + const isHorizontal = props.orientation === "horizontal"; + React2$1.useEffect(() => { + isHorizontal ? onScrollbarXEnabledChange(true) : onScrollbarYEnabledChange(true); + return () => { + isHorizontal ? onScrollbarXEnabledChange(false) : onScrollbarYEnabledChange(false); + }; + }, [isHorizontal, onScrollbarXEnabledChange, onScrollbarYEnabledChange]); + return ( + context.type === "hover" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaScrollbarHover, { ...scrollbarProps, ref: forwardedRef, forceMount }) + : context.type === "scroll" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaScrollbarScroll, { ...scrollbarProps, ref: forwardedRef, forceMount }) + : context.type === "auto" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaScrollbarAuto, { ...scrollbarProps, ref: forwardedRef, forceMount }) + : context.type === "always" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaScrollbarVisible, { ...scrollbarProps, "ref": forwardedRef, "data-state": "visible" }) + : null + ); +}); +ScrollAreaScrollbar.displayName = SCROLLBAR_NAME; +var ScrollAreaScrollbarHover = React2$1.forwardRef((props, forwardedRef) => { + const { forceMount, ...scrollbarProps } = props; + const context = useScrollAreaContext(SCROLLBAR_NAME, props.__scopeScrollArea); + const [visible, setVisible] = React2$1.useState(false); + React2$1.useEffect(() => { + const scrollArea = context.scrollArea; + let hideTimer = 0; + if (scrollArea) { + const handlePointerEnter = () => { + window.clearTimeout(hideTimer); + setVisible(true); + }; + const handlePointerLeave = () => { + hideTimer = window.setTimeout(() => setVisible(false), context.scrollHideDelay); + }; + scrollArea.addEventListener("pointerenter", handlePointerEnter); + scrollArea.addEventListener("pointerleave", handlePointerLeave); + return () => { + window.clearTimeout(hideTimer); + scrollArea.removeEventListener("pointerenter", handlePointerEnter); + scrollArea.removeEventListener("pointerleave", handlePointerLeave); + }; + } + }, [context.scrollArea, context.scrollHideDelay]); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Presence, { + present: forceMount || visible, + children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaScrollbarAuto, { + "data-state": visible ? "visible" : "hidden", + ...scrollbarProps, + "ref": forwardedRef, + }), + }); +}); +var ScrollAreaScrollbarScroll = React2$1.forwardRef((props, forwardedRef) => { + const { forceMount, ...scrollbarProps } = props; + const context = useScrollAreaContext(SCROLLBAR_NAME, props.__scopeScrollArea); + const isHorizontal = props.orientation === "horizontal"; + const debounceScrollEnd = useDebounceCallback(() => send("SCROLL_END"), 100); + const [state, send] = useStateMachine("hidden", { + hidden: { + SCROLL: "scrolling", + }, + scrolling: { + SCROLL_END: "idle", + POINTER_ENTER: "interacting", + }, + interacting: { + SCROLL: "interacting", + POINTER_LEAVE: "idle", + }, + idle: { + HIDE: "hidden", + SCROLL: "scrolling", + POINTER_ENTER: "interacting", + }, + }); + React2$1.useEffect(() => { + if (state === "idle") { + const hideTimer = window.setTimeout(() => send("HIDE"), context.scrollHideDelay); + return () => window.clearTimeout(hideTimer); + } + }, [state, context.scrollHideDelay, send]); + React2$1.useEffect(() => { + const viewport = context.viewport; + const scrollDirection = isHorizontal ? "scrollLeft" : "scrollTop"; + if (viewport) { + let prevScrollPos = viewport[scrollDirection]; + const handleScroll = () => { + const scrollPos = viewport[scrollDirection]; + const hasScrollInDirectionChanged = prevScrollPos !== scrollPos; + if (hasScrollInDirectionChanged) { + send("SCROLL"); + debounceScrollEnd(); + } + prevScrollPos = scrollPos; + }; + viewport.addEventListener("scroll", handleScroll); + return () => viewport.removeEventListener("scroll", handleScroll); + } + }, [context.viewport, isHorizontal, send, debounceScrollEnd]); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Presence, { + present: forceMount || state !== "hidden", + children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaScrollbarVisible, { + "data-state": state === "hidden" ? "hidden" : "visible", + ...scrollbarProps, + "ref": forwardedRef, + "onPointerEnter": composeEventHandlers(props.onPointerEnter, () => send("POINTER_ENTER")), + "onPointerLeave": composeEventHandlers(props.onPointerLeave, () => send("POINTER_LEAVE")), + }), + }); +}); +var ScrollAreaScrollbarAuto = React2$1.forwardRef((props, forwardedRef) => { + const context = useScrollAreaContext(SCROLLBAR_NAME, props.__scopeScrollArea); + const { forceMount, ...scrollbarProps } = props; + const [visible, setVisible] = React2$1.useState(false); + const isHorizontal = props.orientation === "horizontal"; + const handleResize = useDebounceCallback(() => { + if (context.viewport) { + const isOverflowX = context.viewport.offsetWidth < context.viewport.scrollWidth; + const isOverflowY = context.viewport.offsetHeight < context.viewport.scrollHeight; + setVisible(isHorizontal ? isOverflowX : isOverflowY); + } + }, 10); + useResizeObserver$1(context.viewport, handleResize); + useResizeObserver$1(context.content, handleResize); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Presence, { + present: forceMount || visible, + children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaScrollbarVisible, { + "data-state": visible ? "visible" : "hidden", + ...scrollbarProps, + "ref": forwardedRef, + }), + }); +}); +var ScrollAreaScrollbarVisible = React2$1.forwardRef((props, forwardedRef) => { + const { orientation = "vertical", ...scrollbarProps } = props; + const context = useScrollAreaContext(SCROLLBAR_NAME, props.__scopeScrollArea); + const thumbRef = React2$1.useRef(null); + const pointerOffsetRef = React2$1.useRef(0); + const [sizes, setSizes] = React2$1.useState({ + content: 0, + viewport: 0, + scrollbar: { size: 0, paddingStart: 0, paddingEnd: 0 }, + }); + const thumbRatio = getThumbRatio(sizes.viewport, sizes.content); + const commonProps = { + ...scrollbarProps, + sizes, + onSizesChange: setSizes, + hasThumb: Boolean(thumbRatio > 0 && thumbRatio < 1), + onThumbChange: (thumb) => (thumbRef.current = thumb), + onThumbPointerUp: () => (pointerOffsetRef.current = 0), + onThumbPointerDown: (pointerPos) => (pointerOffsetRef.current = pointerPos), + }; + function getScrollPosition(pointerPos, dir) { + return getScrollPositionFromPointer(pointerPos, pointerOffsetRef.current, sizes, dir); + } + if (orientation === "horizontal") { + return /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaScrollbarX, { + ...commonProps, + ref: forwardedRef, + onThumbPositionChange: () => { + if (context.viewport && thumbRef.current) { + const scrollPos = context.viewport.scrollLeft; + const offset = getThumbOffsetFromScroll(scrollPos, sizes, context.dir); + thumbRef.current.style.transform = `translate3d(${offset}px, 0, 0)`; + } + }, + onWheelScroll: (scrollPos) => { + if (context.viewport) context.viewport.scrollLeft = scrollPos; + }, + onDragScroll: (pointerPos) => { + if (context.viewport) { + context.viewport.scrollLeft = getScrollPosition(pointerPos, context.dir); + } + }, + }); + } + if (orientation === "vertical") { + return /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaScrollbarY, { + ...commonProps, + ref: forwardedRef, + onThumbPositionChange: () => { + if (context.viewport && thumbRef.current) { + const scrollPos = context.viewport.scrollTop; + const offset = getThumbOffsetFromScroll(scrollPos, sizes); + thumbRef.current.style.transform = `translate3d(0, ${offset}px, 0)`; + } + }, + onWheelScroll: (scrollPos) => { + if (context.viewport) context.viewport.scrollTop = scrollPos; + }, + onDragScroll: (pointerPos) => { + if (context.viewport) context.viewport.scrollTop = getScrollPosition(pointerPos); + }, + }); + } + return null; +}); +var ScrollAreaScrollbarX = React2$1.forwardRef((props, forwardedRef) => { + const { sizes, onSizesChange, ...scrollbarProps } = props; + const context = useScrollAreaContext(SCROLLBAR_NAME, props.__scopeScrollArea); + const [computedStyle, setComputedStyle] = React2$1.useState(); + const ref = React2$1.useRef(null); + const composeRefs = useComposedRefs(forwardedRef, ref, context.onScrollbarXChange); + React2$1.useEffect(() => { + if (ref.current) setComputedStyle(getComputedStyle(ref.current)); + }, [ref]); + return /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaScrollbarImpl, { + "data-orientation": "horizontal", + ...scrollbarProps, + "ref": composeRefs, + sizes, + "style": { + "bottom": 0, + "left": context.dir === "rtl" ? "var(--radix-scroll-area-corner-width)" : 0, + "right": context.dir === "ltr" ? "var(--radix-scroll-area-corner-width)" : 0, + "--radix-scroll-area-thumb-width": getThumbSize(sizes) + "px", + ...props.style, + }, + "onThumbPointerDown": (pointerPos) => props.onThumbPointerDown(pointerPos.x), + "onDragScroll": (pointerPos) => props.onDragScroll(pointerPos.x), + "onWheelScroll": (event, maxScrollPos) => { + if (context.viewport) { + const scrollPos = context.viewport.scrollLeft + event.deltaX; + props.onWheelScroll(scrollPos); + if (isScrollingWithinScrollbarBounds(scrollPos, maxScrollPos)) { + event.preventDefault(); + } + } + }, + "onResize": () => { + if (ref.current && context.viewport && computedStyle) { + onSizesChange({ + content: context.viewport.scrollWidth, + viewport: context.viewport.offsetWidth, + scrollbar: { + size: ref.current.clientWidth, + paddingStart: toInt(computedStyle.paddingLeft), + paddingEnd: toInt(computedStyle.paddingRight), + }, + }); + } + }, + }); +}); +var ScrollAreaScrollbarY = React2$1.forwardRef((props, forwardedRef) => { + const { sizes, onSizesChange, ...scrollbarProps } = props; + const context = useScrollAreaContext(SCROLLBAR_NAME, props.__scopeScrollArea); + const [computedStyle, setComputedStyle] = React2$1.useState(); + const ref = React2$1.useRef(null); + const composeRefs = useComposedRefs(forwardedRef, ref, context.onScrollbarYChange); + React2$1.useEffect(() => { + if (ref.current) setComputedStyle(getComputedStyle(ref.current)); + }, [ref]); + return /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaScrollbarImpl, { + "data-orientation": "vertical", + ...scrollbarProps, + "ref": composeRefs, + sizes, + "style": { + "top": 0, + "right": context.dir === "ltr" ? 0 : void 0, + "left": context.dir === "rtl" ? 0 : void 0, + "bottom": "var(--radix-scroll-area-corner-height)", + "--radix-scroll-area-thumb-height": getThumbSize(sizes) + "px", + ...props.style, + }, + "onThumbPointerDown": (pointerPos) => props.onThumbPointerDown(pointerPos.y), + "onDragScroll": (pointerPos) => props.onDragScroll(pointerPos.y), + "onWheelScroll": (event, maxScrollPos) => { + if (context.viewport) { + const scrollPos = context.viewport.scrollTop + event.deltaY; + props.onWheelScroll(scrollPos); + if (isScrollingWithinScrollbarBounds(scrollPos, maxScrollPos)) { + event.preventDefault(); + } + } + }, + "onResize": () => { + if (ref.current && context.viewport && computedStyle) { + onSizesChange({ + content: context.viewport.scrollHeight, + viewport: context.viewport.offsetHeight, + scrollbar: { + size: ref.current.clientHeight, + paddingStart: toInt(computedStyle.paddingTop), + paddingEnd: toInt(computedStyle.paddingBottom), + }, + }); + } + }, + }); +}); +var [ScrollbarProvider, useScrollbarContext] = createScrollAreaContext(SCROLLBAR_NAME); +var ScrollAreaScrollbarImpl = React2$1.forwardRef((props, forwardedRef) => { + const { __scopeScrollArea, sizes, hasThumb, onThumbChange, onThumbPointerUp, onThumbPointerDown, onThumbPositionChange, onDragScroll, onWheelScroll, onResize, ...scrollbarProps } = props; + const context = useScrollAreaContext(SCROLLBAR_NAME, __scopeScrollArea); + const [scrollbar, setScrollbar] = React2$1.useState(null); + const composeRefs = useComposedRefs(forwardedRef, setScrollbar); + const rectRef = React2$1.useRef(null); + const prevWebkitUserSelectRef = React2$1.useRef(""); + const viewport = context.viewport; + const maxScrollPos = sizes.content - sizes.viewport; + const handleWheelScroll = useCallbackRef(onWheelScroll); + const handleThumbPositionChange = useCallbackRef(onThumbPositionChange); + const handleResize = useDebounceCallback(onResize, 10); + function handleDragScroll(event) { + if (rectRef.current) { + const x = event.clientX - rectRef.current.left; + const y = event.clientY - rectRef.current.top; + onDragScroll({ x, y }); + } + } + React2$1.useEffect(() => { + const handleWheel = (event) => { + const element = event.target; + const isScrollbarWheel = scrollbar?.contains(element); + if (isScrollbarWheel) handleWheelScroll(event, maxScrollPos); + }; + document.addEventListener("wheel", handleWheel, { passive: false }); + return () => document.removeEventListener("wheel", handleWheel, { passive: false }); + }, [viewport, scrollbar, maxScrollPos, handleWheelScroll]); + React2$1.useEffect(handleThumbPositionChange, [sizes, handleThumbPositionChange]); + useResizeObserver$1(scrollbar, handleResize); + useResizeObserver$1(context.content, handleResize); + return /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollbarProvider, { + scope: __scopeScrollArea, + scrollbar, + hasThumb, + onThumbChange: useCallbackRef(onThumbChange), + onThumbPointerUp: useCallbackRef(onThumbPointerUp), + onThumbPositionChange: handleThumbPositionChange, + onThumbPointerDown: useCallbackRef(onThumbPointerDown), + children: /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.div, { + ...scrollbarProps, + ref: composeRefs, + style: { position: "absolute", ...scrollbarProps.style }, + onPointerDown: composeEventHandlers(props.onPointerDown, (event) => { + const mainPointer = 0; + if (event.button === mainPointer) { + const element = event.target; + element.setPointerCapture(event.pointerId); + rectRef.current = scrollbar.getBoundingClientRect(); + prevWebkitUserSelectRef.current = document.body.style.webkitUserSelect; + document.body.style.webkitUserSelect = "none"; + if (context.viewport) context.viewport.style.scrollBehavior = "auto"; + handleDragScroll(event); + } + }), + onPointerMove: composeEventHandlers(props.onPointerMove, handleDragScroll), + onPointerUp: composeEventHandlers(props.onPointerUp, (event) => { + const element = event.target; + if (element.hasPointerCapture(event.pointerId)) { + element.releasePointerCapture(event.pointerId); + } + document.body.style.webkitUserSelect = prevWebkitUserSelectRef.current; + if (context.viewport) context.viewport.style.scrollBehavior = ""; + rectRef.current = null; + }), + }), + }); +}); +var THUMB_NAME$1 = "ScrollAreaThumb"; +var ScrollAreaThumb = React2$1.forwardRef((props, forwardedRef) => { + const { forceMount, ...thumbProps } = props; + const scrollbarContext = useScrollbarContext(THUMB_NAME$1, props.__scopeScrollArea); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Presence, { + present: forceMount || scrollbarContext.hasThumb, + children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaThumbImpl, { ref: forwardedRef, ...thumbProps }), + }); +}); +var ScrollAreaThumbImpl = React2$1.forwardRef((props, forwardedRef) => { + const { __scopeScrollArea, style, ...thumbProps } = props; + const scrollAreaContext = useScrollAreaContext(THUMB_NAME$1, __scopeScrollArea); + const scrollbarContext = useScrollbarContext(THUMB_NAME$1, __scopeScrollArea); + const { onThumbPositionChange } = scrollbarContext; + const composedRef = useComposedRefs(forwardedRef, scrollbarContext.onThumbChange); + const removeUnlinkedScrollListenerRef = React2$1.useRef(void 0); + const debounceScrollEnd = useDebounceCallback(() => { + if (removeUnlinkedScrollListenerRef.current) { + removeUnlinkedScrollListenerRef.current(); + removeUnlinkedScrollListenerRef.current = void 0; + } + }, 100); + React2$1.useEffect(() => { + const viewport = scrollAreaContext.viewport; + if (viewport) { + const handleScroll = () => { + debounceScrollEnd(); + if (!removeUnlinkedScrollListenerRef.current) { + const listener = addUnlinkedScrollListener(viewport, onThumbPositionChange); + removeUnlinkedScrollListenerRef.current = listener; + onThumbPositionChange(); + } + }; + onThumbPositionChange(); + viewport.addEventListener("scroll", handleScroll); + return () => viewport.removeEventListener("scroll", handleScroll); + } + }, [scrollAreaContext.viewport, debounceScrollEnd, onThumbPositionChange]); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.div, { + "data-state": scrollbarContext.hasThumb ? "visible" : "hidden", + ...thumbProps, + "ref": composedRef, + "style": { + width: "var(--radix-scroll-area-thumb-width)", + height: "var(--radix-scroll-area-thumb-height)", + ...style, + }, + "onPointerDownCapture": composeEventHandlers(props.onPointerDownCapture, (event) => { + const thumb = event.target; + const thumbRect = thumb.getBoundingClientRect(); + const x = event.clientX - thumbRect.left; + const y = event.clientY - thumbRect.top; + scrollbarContext.onThumbPointerDown({ x, y }); + }), + "onPointerUp": composeEventHandlers(props.onPointerUp, scrollbarContext.onThumbPointerUp), + }); +}); +ScrollAreaThumb.displayName = THUMB_NAME$1; +var CORNER_NAME = "ScrollAreaCorner"; +var ScrollAreaCorner = React2$1.forwardRef((props, forwardedRef) => { + const context = useScrollAreaContext(CORNER_NAME, props.__scopeScrollArea); + const hasBothScrollbarsVisible = Boolean(context.scrollbarX && context.scrollbarY); + const hasCorner = context.type !== "scroll" && hasBothScrollbarsVisible; + return hasCorner ? /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaCornerImpl, { ...props, ref: forwardedRef }) : null; +}); +ScrollAreaCorner.displayName = CORNER_NAME; +var ScrollAreaCornerImpl = React2$1.forwardRef((props, forwardedRef) => { + const { __scopeScrollArea, ...cornerProps } = props; + const context = useScrollAreaContext(CORNER_NAME, __scopeScrollArea); + const [width, setWidth] = React2$1.useState(0); + const [height, setHeight] = React2$1.useState(0); + const hasSize = Boolean(width && height); + useResizeObserver$1(context.scrollbarX, () => { + const height2 = context.scrollbarX?.offsetHeight || 0; + context.onCornerHeightChange(height2); + setHeight(height2); + }); + useResizeObserver$1(context.scrollbarY, () => { + const width2 = context.scrollbarY?.offsetWidth || 0; + context.onCornerWidthChange(width2); + setWidth(width2); + }); + return hasSize ? + /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.div, { + ...cornerProps, + ref: forwardedRef, + style: { + width, + height, + position: "absolute", + right: context.dir === "ltr" ? 0 : void 0, + left: context.dir === "rtl" ? 0 : void 0, + bottom: 0, + ...props.style, + }, + }) + : null; +}); +function toInt(value) { + return value ? parseInt(value, 10) : 0; +} +function getThumbRatio(viewportSize, contentSize) { + const ratio = viewportSize / contentSize; + return isNaN(ratio) ? 0 : ratio; +} +function getThumbSize(sizes) { + const ratio = getThumbRatio(sizes.viewport, sizes.content); + const scrollbarPadding = sizes.scrollbar.paddingStart + sizes.scrollbar.paddingEnd; + const thumbSize = (sizes.scrollbar.size - scrollbarPadding) * ratio; + return Math.max(thumbSize, 18); +} +function getScrollPositionFromPointer(pointerPos, pointerOffset, sizes, dir = "ltr") { + const thumbSizePx = getThumbSize(sizes); + const thumbCenter = thumbSizePx / 2; + const offset = pointerOffset || thumbCenter; + const thumbOffsetFromEnd = thumbSizePx - offset; + const minPointerPos = sizes.scrollbar.paddingStart + offset; + const maxPointerPos = sizes.scrollbar.size - sizes.scrollbar.paddingEnd - thumbOffsetFromEnd; + const maxScrollPos = sizes.content - sizes.viewport; + const scrollRange = dir === "ltr" ? [0, maxScrollPos] : [maxScrollPos * -1, 0]; + const interpolate = linearScale([minPointerPos, maxPointerPos], scrollRange); + return interpolate(pointerPos); +} +function getThumbOffsetFromScroll(scrollPos, sizes, dir = "ltr") { + const thumbSizePx = getThumbSize(sizes); + const scrollbarPadding = sizes.scrollbar.paddingStart + sizes.scrollbar.paddingEnd; + const scrollbar = sizes.scrollbar.size - scrollbarPadding; + const maxScrollPos = sizes.content - sizes.viewport; + const maxThumbPos = scrollbar - thumbSizePx; + const scrollClampRange = dir === "ltr" ? [0, maxScrollPos] : [maxScrollPos * -1, 0]; + const scrollWithoutMomentum = clamp(scrollPos, scrollClampRange); + const interpolate = linearScale([0, maxScrollPos], [0, maxThumbPos]); + return interpolate(scrollWithoutMomentum); +} +function linearScale(input, output) { + return (value) => { + if (input[0] === input[1] || output[0] === output[1]) return output[0]; + const ratio = (output[1] - output[0]) / (input[1] - input[0]); + return output[0] + ratio * (value - input[0]); + }; +} +function isScrollingWithinScrollbarBounds(scrollPos, maxScrollPos) { + return scrollPos > 0 && scrollPos < maxScrollPos; +} +var addUnlinkedScrollListener = (node, handler = () => {}) => { + let prevPosition = { left: node.scrollLeft, top: node.scrollTop }; + let rAF = 0; + (function loop() { + const position = { left: node.scrollLeft, top: node.scrollTop }; + const isHorizontalScroll = prevPosition.left !== position.left; + const isVerticalScroll = prevPosition.top !== position.top; + if (isHorizontalScroll || isVerticalScroll) handler(); + prevPosition = position; + rAF = window.requestAnimationFrame(loop); + })(); + return () => window.cancelAnimationFrame(rAF); +}; +function useDebounceCallback(callback, delay) { + const handleCallback = useCallbackRef(callback); + const debounceTimerRef = React2$1.useRef(0); + React2$1.useEffect(() => () => window.clearTimeout(debounceTimerRef.current), []); + return React2$1.useCallback(() => { + window.clearTimeout(debounceTimerRef.current); + debounceTimerRef.current = window.setTimeout(handleCallback, delay); + }, [handleCallback, delay]); +} +function useResizeObserver$1(element, onResize) { + const handleResize = useCallbackRef(onResize); + useLayoutEffect2(() => { + let rAF = 0; + if (element) { + const resizeObserver = new ResizeObserver(() => { + cancelAnimationFrame(rAF); + rAF = window.requestAnimationFrame(handleResize); + }); + resizeObserver.observe(element); + return () => { + window.cancelAnimationFrame(rAF); + resizeObserver.unobserve(element); + }; + } + }, [element, handleResize]); +} +var Root$1 = ScrollArea$1; +var Viewport = ScrollAreaViewport; +var Corner = ScrollAreaCorner; + +// src/separator.tsx +const React$f = await importShared("react"); +var NAME = "Separator"; +var DEFAULT_ORIENTATION = "horizontal"; +var ORIENTATIONS = ["horizontal", "vertical"]; +var Separator$1 = React$f.forwardRef((props, forwardedRef) => { + const { decorative, orientation: orientationProp = DEFAULT_ORIENTATION, ...domProps } = props; + const orientation = isValidOrientation(orientationProp) ? orientationProp : DEFAULT_ORIENTATION; + const ariaOrientation = orientation === "vertical" ? orientation : void 0; + const semanticProps = decorative ? { role: "none" } : { "aria-orientation": ariaOrientation, "role": "separator" }; + return /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.div, { + "data-orientation": orientation, + ...semanticProps, + ...domProps, + "ref": forwardedRef, + }); +}); +Separator$1.displayName = NAME; +function isValidOrientation(orientation) { + return ORIENTATIONS.includes(orientation); +} +var Root = Separator$1; + +// src/dialog.tsx +const React$e = await importShared("react"); +var DIALOG_NAME = "Dialog"; +var [createDialogContext, createDialogScope] = createContextScope(DIALOG_NAME); +var [DialogProvider, useDialogContext] = createDialogContext(DIALOG_NAME); +var Dialog$1 = (props) => { + const { __scopeDialog, children, open: openProp, defaultOpen, onOpenChange, modal = true } = props; + const triggerRef = React$e.useRef(null); + const contentRef = React$e.useRef(null); + const [open, setOpen] = useControllableState({ + prop: openProp, + defaultProp: defaultOpen ?? false, + onChange: onOpenChange, + caller: DIALOG_NAME, + }); + return /* @__PURE__ */ jsxRuntimeExports.jsx(DialogProvider, { + scope: __scopeDialog, + triggerRef, + contentRef, + contentId: useId(), + titleId: useId(), + descriptionId: useId(), + open, + onOpenChange: setOpen, + onOpenToggle: React$e.useCallback(() => setOpen((prevOpen) => !prevOpen), [setOpen]), + modal, + children, + }); +}; +Dialog$1.displayName = DIALOG_NAME; +var TRIGGER_NAME$3 = "DialogTrigger"; +var DialogTrigger$1 = React$e.forwardRef((props, forwardedRef) => { + const { __scopeDialog, ...triggerProps } = props; + const context = useDialogContext(TRIGGER_NAME$3, __scopeDialog); + const composedTriggerRef = useComposedRefs(forwardedRef, context.triggerRef); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.button, { + "type": "button", + "aria-haspopup": "dialog", + "aria-expanded": context.open, + "aria-controls": context.open ? context.contentId : void 0, + "data-state": getState$2(context.open), + ...triggerProps, + "ref": composedTriggerRef, + "onClick": composeEventHandlers(props.onClick, context.onOpenToggle), + }); +}); +DialogTrigger$1.displayName = TRIGGER_NAME$3; +var PORTAL_NAME$1 = "DialogPortal"; +var [PortalProvider, usePortalContext] = createDialogContext(PORTAL_NAME$1, { + forceMount: void 0, +}); +var DialogPortal$1 = (props) => { + const { __scopeDialog, forceMount, children, container } = props; + const context = useDialogContext(PORTAL_NAME$1, __scopeDialog); + return /* @__PURE__ */ jsxRuntimeExports.jsx(PortalProvider, { + scope: __scopeDialog, + forceMount, + children: React$e.Children.map(children, (child) => + /* @__PURE__ */ jsxRuntimeExports.jsx(Presence, { + present: forceMount || context.open, + children: /* @__PURE__ */ jsxRuntimeExports.jsx(Portal, { asChild: true, container, children: child }), + }), + ), + }); +}; +DialogPortal$1.displayName = PORTAL_NAME$1; +var OVERLAY_NAME$1 = "DialogOverlay"; +var DialogOverlay$1 = React$e.forwardRef((props, forwardedRef) => { + const portalContext = usePortalContext(OVERLAY_NAME$1, props.__scopeDialog); + const { forceMount = portalContext.forceMount, ...overlayProps } = props; + const context = useDialogContext(OVERLAY_NAME$1, props.__scopeDialog); + return context.modal ? + /* @__PURE__ */ jsxRuntimeExports.jsx(Presence, { + present: forceMount || context.open, + children: /* @__PURE__ */ jsxRuntimeExports.jsx(DialogOverlayImpl, { ...overlayProps, ref: forwardedRef }), + }) + : null; +}); +DialogOverlay$1.displayName = OVERLAY_NAME$1; +var Slot = createSlot("DialogOverlay.RemoveScroll"); +var DialogOverlayImpl = React$e.forwardRef((props, forwardedRef) => { + const { __scopeDialog, ...overlayProps } = props; + const context = useDialogContext(OVERLAY_NAME$1, __scopeDialog); + const registerDismissableSurface = useDismissableLayerSurface(); + const composedRefs = useComposedRefs(forwardedRef, registerDismissableSurface); + return ( + // Make sure `Content` is scrollable even when it doesn't live inside `RemoveScroll` + // ie. when `Overlay` and `Content` are siblings + /* @__PURE__ */ jsxRuntimeExports.jsx(ReactRemoveScroll, { + as: Slot, + allowPinchZoom: true, + shards: [context.contentRef], + children: /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.div, { + "data-state": getState$2(context.open), + ...overlayProps, + "ref": composedRefs, + "style": { pointerEvents: "auto", ...overlayProps.style }, + }), + }) + ); +}); +var CONTENT_NAME$1 = "DialogContent"; +var DialogContent$1 = React$e.forwardRef((props, forwardedRef) => { + const portalContext = usePortalContext(CONTENT_NAME$1, props.__scopeDialog); + const { forceMount = portalContext.forceMount, ...contentProps } = props; + const context = useDialogContext(CONTENT_NAME$1, props.__scopeDialog); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Presence, { + present: forceMount || context.open, + children: + context.modal ? + /* @__PURE__ */ jsxRuntimeExports.jsx(DialogContentModal, { ...contentProps, ref: forwardedRef }) + : /* @__PURE__ */ jsxRuntimeExports.jsx(DialogContentNonModal, { ...contentProps, ref: forwardedRef }), + }); +}); +DialogContent$1.displayName = CONTENT_NAME$1; +var DialogContentModal = React$e.forwardRef((props, forwardedRef) => { + const context = useDialogContext(CONTENT_NAME$1, props.__scopeDialog); + const contentRef = React$e.useRef(null); + const composedRefs = useComposedRefs(forwardedRef, context.contentRef, contentRef); + React$e.useEffect(() => { + const content = contentRef.current; + if (content) return hideOthers(content); + }, []); + return /* @__PURE__ */ jsxRuntimeExports.jsx(DialogContentImpl, { + ...props, + ref: composedRefs, + trapFocus: context.open, + disableOutsidePointerEvents: context.open, + onCloseAutoFocus: composeEventHandlers(props.onCloseAutoFocus, (event) => { + event.preventDefault(); + context.triggerRef.current?.focus(); + }), + onPointerDownOutside: composeEventHandlers(props.onPointerDownOutside, (event) => { + const originalEvent = event.detail.originalEvent; + const ctrlLeftClick = originalEvent.button === 0 && originalEvent.ctrlKey === true; + const isRightClick = originalEvent.button === 2 || ctrlLeftClick; + if (isRightClick) event.preventDefault(); + }), + onFocusOutside: composeEventHandlers(props.onFocusOutside, (event) => event.preventDefault()), + }); +}); +var DialogContentNonModal = React$e.forwardRef((props, forwardedRef) => { + const context = useDialogContext(CONTENT_NAME$1, props.__scopeDialog); + const hasInteractedOutsideRef = React$e.useRef(false); + const hasPointerDownOutsideRef = React$e.useRef(false); + return /* @__PURE__ */ jsxRuntimeExports.jsx(DialogContentImpl, { + ...props, + ref: forwardedRef, + trapFocus: false, + disableOutsidePointerEvents: false, + onCloseAutoFocus: (event) => { + props.onCloseAutoFocus?.(event); + if (!event.defaultPrevented) { + if (!hasInteractedOutsideRef.current) context.triggerRef.current?.focus(); + event.preventDefault(); + } + hasInteractedOutsideRef.current = false; + hasPointerDownOutsideRef.current = false; + }, + onInteractOutside: (event) => { + props.onInteractOutside?.(event); + if (!event.defaultPrevented) { + hasInteractedOutsideRef.current = true; + if (event.detail.originalEvent.type === "pointerdown") { + hasPointerDownOutsideRef.current = true; + } + } + const target = event.target; + const targetIsTrigger = context.triggerRef.current?.contains(target); + if (targetIsTrigger) event.preventDefault(); + if (event.detail.originalEvent.type === "focusin" && hasPointerDownOutsideRef.current) { + event.preventDefault(); + } + }, + }); +}); +var DialogContentImpl = React$e.forwardRef((props, forwardedRef) => { + const { __scopeDialog, trapFocus, onOpenAutoFocus, onCloseAutoFocus, ...contentProps } = props; + const context = useDialogContext(CONTENT_NAME$1, __scopeDialog); + useFocusGuards(); + return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { + children: /* @__PURE__ */ jsxRuntimeExports.jsx(FocusScope, { + asChild: true, + loop: true, + trapped: trapFocus, + onMountAutoFocus: onOpenAutoFocus, + onUnmountAutoFocus: onCloseAutoFocus, + children: /* @__PURE__ */ jsxRuntimeExports.jsx(DismissableLayer, { + "role": "dialog", + "id": context.contentId, + "aria-describedby": context.descriptionId, + "aria-labelledby": context.titleId, + "data-state": getState$2(context.open), + ...contentProps, + "ref": forwardedRef, + "deferPointerDownOutside": true, + "onDismiss": () => context.onOpenChange(false), + }), + }), + }); +}); +var TITLE_NAME$1 = "DialogTitle"; +var DialogTitle$1 = React$e.forwardRef((props, forwardedRef) => { + const { __scopeDialog, ...titleProps } = props; + const context = useDialogContext(TITLE_NAME$1, __scopeDialog); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.h2, { id: context.titleId, ...titleProps, ref: forwardedRef }); +}); +DialogTitle$1.displayName = TITLE_NAME$1; +var DESCRIPTION_NAME$1 = "DialogDescription"; +var DialogDescription$1 = React$e.forwardRef((props, forwardedRef) => { + const { __scopeDialog, ...descriptionProps } = props; + const context = useDialogContext(DESCRIPTION_NAME$1, __scopeDialog); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.p, { id: context.descriptionId, ...descriptionProps, ref: forwardedRef }); +}); +DialogDescription$1.displayName = DESCRIPTION_NAME$1; +var CLOSE_NAME = "DialogClose"; +var DialogClose$1 = React$e.forwardRef((props, forwardedRef) => { + const { __scopeDialog, ...closeProps } = props; + const context = useDialogContext(CLOSE_NAME, __scopeDialog); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.button, { + type: "button", + ...closeProps, + ref: forwardedRef, + onClick: composeEventHandlers(props.onClick, () => context.onOpenChange(false)), + }); +}); +DialogClose$1.displayName = CLOSE_NAME; +function getState$2(open) { + return open ? "open" : "closed"; +} + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const ArrowDown = createLucideIcon("ArrowDown", [ + ["path", { d: "M12 5v14", key: "s699le" }], + ["path", { d: "m19 12-7 7-7-7", key: "1idqje" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const ArrowRight = createLucideIcon("ArrowRight", [ + ["path", { d: "M5 12h14", key: "1ays0h" }], + ["path", { d: "m12 5 7 7-7 7", key: "xquz4c" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const ArrowUp = createLucideIcon("ArrowUp", [ + ["path", { d: "m5 12 7-7 7 7", key: "hav0vg" }], + ["path", { d: "M12 19V5", key: "x0mq9r" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Check = createLucideIcon("Check", [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const ChevronDown = createLucideIcon("ChevronDown", [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const ChevronLeft = createLucideIcon("ChevronLeft", [["path", { d: "m15 18-6-6 6-6", key: "1wnfg3" }]]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const ChevronRight = createLucideIcon("ChevronRight", [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const ChevronUp = createLucideIcon("ChevronUp", [["path", { d: "m18 15-6-6-6 6", key: "153udz" }]]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const ChevronsLeft = createLucideIcon("ChevronsLeft", [ + ["path", { d: "m11 17-5-5 5-5", key: "13zhaf" }], + ["path", { d: "m18 17-5-5 5-5", key: "h8a8et" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const ChevronsRight = createLucideIcon("ChevronsRight", [ + ["path", { d: "m6 17 5-5-5-5", key: "xnjwq" }], + ["path", { d: "m13 17 5-5-5-5", key: "17xmmf" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const ChevronsUpDown = createLucideIcon("ChevronsUpDown", [ + ["path", { d: "m7 15 5 5 5-5", key: "1hf1tw" }], + ["path", { d: "m7 9 5-5 5 5", key: "sgt6xg" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const CircleAlert = createLucideIcon("CircleAlert", [ + ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }], + ["line", { x1: "12", x2: "12", y1: "8", y2: "12", key: "1pkeuh" }], + ["line", { x1: "12", x2: "12.01", y1: "16", y2: "16", key: "4dfq90" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const CircleCheckBig = createLucideIcon("CircleCheckBig", [ + ["path", { d: "M21.801 10A10 10 0 1 1 17 3.335", key: "yps3ct" }], + ["path", { d: "m9 11 3 3L22 4", key: "1pflzl" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const CircleCheck = createLucideIcon("CircleCheck", [ + ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }], + ["path", { d: "m9 12 2 2 4-4", key: "dzmm74" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const CircleHelp = createLucideIcon("CircleHelp", [ + ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }], + ["path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3", key: "1u773s" }], + ["path", { d: "M12 17h.01", key: "p32p05" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const CircleUserRound = createLucideIcon("CircleUserRound", [ + ["path", { d: "M18 20a6 6 0 0 0-12 0", key: "1qehca" }], + ["circle", { cx: "12", cy: "10", r: "4", key: "1h16sb" }], + ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const CircleX = createLucideIcon("CircleX", [ + ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }], + ["path", { d: "m15 9-6 6", key: "1uzhvr" }], + ["path", { d: "m9 9 6 6", key: "z0biqf" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Code$1 = createLucideIcon("Code", [ + ["polyline", { points: "16 18 22 12 16 6", key: "z7tu5w" }], + ["polyline", { points: "8 6 2 12 8 18", key: "1eg1df" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Copy = createLucideIcon("Copy", [ + ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }], + ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", key: "zix9uf" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Download = createLucideIcon("Download", [ + ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }], + ["polyline", { points: "7 10 12 15 17 10", key: "2ggqvy" }], + ["line", { x1: "12", x2: "12", y1: "15", y2: "3", key: "1vk2je" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const EllipsisVertical = createLucideIcon("EllipsisVertical", [ + ["circle", { cx: "12", cy: "12", r: "1", key: "41hilf" }], + ["circle", { cx: "12", cy: "5", r: "1", key: "gxeob9" }], + ["circle", { cx: "12", cy: "19", r: "1", key: "lyex9k" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Ellipsis = createLucideIcon("Ellipsis", [ + ["circle", { cx: "12", cy: "12", r: "1", key: "41hilf" }], + ["circle", { cx: "19", cy: "12", r: "1", key: "1wjl8i" }], + ["circle", { cx: "5", cy: "12", r: "1", key: "1pcz8c" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const ExternalLink = createLucideIcon("ExternalLink", [ + ["path", { d: "M15 3h6v6", key: "1q9fwt" }], + ["path", { d: "M10 14 21 3", key: "gplh6r" }], + ["path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6", key: "a6xqqp" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const EyeOff = createLucideIcon("EyeOff", [ + [ + "path", + { + d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49", + key: "ct8e1f", + }, + ], + ["path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242", key: "151rxh" }], + [ + "path", + { + d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143", + key: "13bj9a", + }, + ], + ["path", { d: "m2 2 20 20", key: "1ooewy" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Eye = createLucideIcon("Eye", [ + [ + "path", + { + d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0", + key: "1nclc0", + }, + ], + ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const File$1 = createLucideIcon("File", [ + ["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z", key: "1rqfz7" }], + ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4", key: "tnqrlb" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Filter = createLucideIcon("Filter", [["polygon", { points: "22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3", key: "1yg77f" }]]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Folder = createLucideIcon("Folder", [ + [ + "path", + { + d: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z", + key: "1kt360", + }, + ], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Grid3x3 = createLucideIcon("Grid3x3", [ + ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }], + ["path", { d: "M3 9h18", key: "1pudct" }], + ["path", { d: "M3 15h18", key: "5xshup" }], + ["path", { d: "M9 3v18", key: "fh3hqa" }], + ["path", { d: "M15 3v18", key: "14nvp0" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const History = createLucideIcon("History", [ + ["path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8", key: "1357e3" }], + ["path", { d: "M3 3v5h5", key: "1xhq8a" }], + ["path", { d: "M12 7v5l4 2", key: "1fdv2h" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Image = createLucideIcon("Image", [ + ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }], + ["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }], + ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Info = createLucideIcon("Info", [ + ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }], + ["path", { d: "M12 16v-4", key: "1dtifu" }], + ["path", { d: "M12 8h.01", key: "e9boi3" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Key = createLucideIcon("Key", [ + ["path", { d: "m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4", key: "g0fldk" }], + ["path", { d: "m21 2-9.6 9.6", key: "1j0ho8" }], + ["circle", { cx: "7.5", cy: "15.5", r: "5.5", key: "yqb3hr" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Link = createLucideIcon("Link", [ + ["path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71", key: "1cjeqo" }], + ["path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71", key: "19qd67" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const LoaderCircle = createLucideIcon("LoaderCircle", [["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Menu = createLucideIcon("Menu", [ + ["line", { x1: "4", x2: "20", y1: "12", y2: "12", key: "1e0a9i" }], + ["line", { x1: "4", x2: "20", y1: "6", y2: "6", key: "1owob3" }], + ["line", { x1: "4", x2: "20", y1: "18", y2: "18", key: "yk5zj1" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Minus = createLucideIcon("Minus", [["path", { d: "M5 12h14", key: "1ays0h" }]]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const PenLine = createLucideIcon("PenLine", [ + ["path", { d: "M12 20h9", key: "t2du7b" }], + [ + "path", + { + d: "M16.376 3.622a1 1 0 0 1 3.002 3.002L7.368 18.635a2 2 0 0 1-.855.506l-2.872.838a.5.5 0 0 1-.62-.62l.838-2.872a2 2 0 0 1 .506-.854z", + key: "1ykcvy", + }, + ], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Plus = createLucideIcon("Plus", [ + ["path", { d: "M5 12h14", key: "1ays0h" }], + ["path", { d: "M12 5v14", key: "s699le" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const RefreshCw = createLucideIcon("RefreshCw", [ + ["path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8", key: "v9h5vc" }], + ["path", { d: "M21 3v5h-5", key: "1q7to0" }], + ["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16", key: "3uifl3" }], + ["path", { d: "M8 16H3v5", key: "1cv678" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Search = createLucideIcon("Search", [ + ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }], + ["path", { d: "m21 21-4.3-4.3", key: "1qie3q" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Send = createLucideIcon("Send", [ + [ + "path", + { + d: "M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z", + key: "1ffxy3", + }, + ], + ["path", { d: "m21.854 2.147-10.94 10.939", key: "12cjpa" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Settings = createLucideIcon("Settings", [ + [ + "path", + { + d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z", + key: "1qme2f", + }, + ], + ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const SlidersHorizontal = createLucideIcon("SlidersHorizontal", [ + ["line", { x1: "21", x2: "14", y1: "4", y2: "4", key: "obuewd" }], + ["line", { x1: "10", x2: "3", y1: "4", y2: "4", key: "1q6298" }], + ["line", { x1: "21", x2: "12", y1: "12", y2: "12", key: "1iu8h1" }], + ["line", { x1: "8", x2: "3", y1: "12", y2: "12", key: "ntss68" }], + ["line", { x1: "21", x2: "16", y1: "20", y2: "20", key: "14d8ph" }], + ["line", { x1: "12", x2: "3", y1: "20", y2: "20", key: "m0wm8r" }], + ["line", { x1: "14", x2: "14", y1: "2", y2: "6", key: "14e1ph" }], + ["line", { x1: "8", x2: "8", y1: "10", y2: "14", key: "1i6ji0" }], + ["line", { x1: "16", x2: "16", y1: "18", y2: "22", key: "1lctlv" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Sparkles = createLucideIcon("Sparkles", [ + [ + "path", + { + d: "M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z", + key: "4pj2yx", + }, + ], + ["path", { d: "M20 3v4", key: "1olli1" }], + ["path", { d: "M22 5h-4", key: "1gvqau" }], + ["path", { d: "M4 17v2", key: "vumght" }], + ["path", { d: "M5 18H3", key: "zchphs" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Trash2 = createLucideIcon("Trash2", [ + ["path", { d: "M3 6h18", key: "d0wm0j" }], + ["path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6", key: "4alrt4" }], + ["path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2", key: "v07s0e" }], + ["line", { x1: "10", x2: "10", y1: "11", y2: "17", key: "1uufr5" }], + ["line", { x1: "14", x2: "14", y1: "11", y2: "17", key: "xtxkd" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const TriangleAlert = createLucideIcon("TriangleAlert", [ + [ + "path", + { + d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3", + key: "wmoenq", + }, + ], + ["path", { d: "M12 9v4", key: "juzpu7" }], + ["path", { d: "M12 17h.01", key: "p32p05" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Upload = createLucideIcon("Upload", [ + ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }], + ["polyline", { points: "17 8 12 3 7 8", key: "t8dd8p" }], + ["line", { x1: "12", x2: "12", y1: "3", y2: "15", key: "widbto" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Users = createLucideIcon("Users", [ + ["path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2", key: "1yyitq" }], + ["circle", { cx: "9", cy: "7", r: "4", key: "nufk8" }], + ["path", { d: "M22 21v-2a4 4 0 0 0-3-3.87", key: "kshegd" }], + ["path", { d: "M16 3.13a4 4 0 0 1 0 7.75", key: "1da9ce" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const WandSparkles = createLucideIcon("WandSparkles", [ + [ + "path", + { + d: "m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72", + key: "ul74o6", + }, + ], + ["path", { d: "m14 7 3 3", key: "1r5n42" }], + ["path", { d: "M5 6v4", key: "ilb8ba" }], + ["path", { d: "M19 14v4", key: "blhpug" }], + ["path", { d: "M10 2v2", key: "7u0qdc" }], + ["path", { d: "M7 8H3", key: "zfb6yr" }], + ["path", { d: "M21 16h-4", key: "1cnmox" }], + ["path", { d: "M11 3H9", key: "1obp7u" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const X = createLucideIcon("X", [ + ["path", { d: "M18 6 6 18", key: "1bl5f8" }], + ["path", { d: "m6 6 12 12", key: "d8bk6v" }], +]); + +/** + * @license lucide-react v0.468.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + */ + +const Zap = createLucideIcon("Zap", [ + [ + "path", + { + d: "M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z", + key: "1xq2db", + }, + ], +]); + +// src/switch.tsx +const React$d = await importShared("react"); +var SWITCH_NAME = "Switch"; +var [createSwitchContext] = createContextScope(SWITCH_NAME); +var [SwitchProviderImpl, useSwitchContext] = createSwitchContext(SWITCH_NAME); +function SwitchProvider(props) { + const { + __scopeSwitch, + checked: checkedProp, + children, + defaultChecked, + disabled, + form, + name, + onCheckedChange, + required, + value = "on", + // @ts-expect-error + internal_do_not_use_render, + } = props; + const [checked, setChecked] = useControllableState({ + prop: checkedProp, + defaultProp: defaultChecked ?? false, + onChange: onCheckedChange, + caller: SWITCH_NAME, + }); + const [control, setControl] = React$d.useState(null); + const [bubbleInput, setBubbleInput] = React$d.useState(null); + const hasConsumerStoppedPropagationRef = React$d.useRef(false); + const isFormControl = + control ? + !!form || !!control.closest("form") + // We set this to true by default so that events bubble to forms without JS (SSR) + : true; + const context = { + checked, + setChecked, + disabled, + control, + setControl, + name, + form, + value, + hasConsumerStoppedPropagationRef, + required, + defaultChecked, + isFormControl, + bubbleInput, + setBubbleInput, + }; + return /* @__PURE__ */ jsxRuntimeExports.jsx(SwitchProviderImpl, { + scope: __scopeSwitch, + ...context, + children: isFunction$2(internal_do_not_use_render) ? internal_do_not_use_render(context) : children, + }); +} +var TRIGGER_NAME$2 = "SwitchTrigger"; +var SwitchTrigger = React$d.forwardRef(({ __scopeSwitch, onClick, ...switchProps }, forwardedRef) => { + const { value, disabled, checked, required, setControl, setChecked, hasConsumerStoppedPropagationRef, isFormControl, bubbleInput } = useSwitchContext(TRIGGER_NAME$2, __scopeSwitch); + const composedRefs = useComposedRefs(forwardedRef, setControl); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.button, { + "type": "button", + "role": "switch", + "aria-checked": checked, + "aria-required": required, + "data-state": getState$1(checked), + "data-disabled": disabled ? "" : void 0, + disabled, + value, + ...switchProps, + "ref": composedRefs, + "onClick": composeEventHandlers(onClick, (event) => { + setChecked((prevChecked) => !prevChecked); + if (bubbleInput && isFormControl) { + hasConsumerStoppedPropagationRef.current = event.isPropagationStopped(); + if (!hasConsumerStoppedPropagationRef.current) event.stopPropagation(); + } + }), + }); +}); +SwitchTrigger.displayName = TRIGGER_NAME$2; +var Switch$1 = React$d.forwardRef((props, forwardedRef) => { + const { __scopeSwitch, name, checked, defaultChecked, required, disabled, value, onCheckedChange, form, ...switchProps } = props; + return /* @__PURE__ */ jsxRuntimeExports.jsx(SwitchProvider, { + __scopeSwitch, + checked, + defaultChecked, + disabled, + required, + onCheckedChange, + name, + form, + value, + internal_do_not_use_render: ({ isFormControl }) => + /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(SwitchTrigger, { + ...switchProps, + ref: forwardedRef, + __scopeSwitch, + }), + isFormControl && + /* @__PURE__ */ jsxRuntimeExports.jsx(SwitchBubbleInput, { + __scopeSwitch, + }), + ], + }), + }); +}); +Switch$1.displayName = SWITCH_NAME; +var THUMB_NAME = "SwitchThumb"; +var SwitchThumb = React$d.forwardRef((props, forwardedRef) => { + const { __scopeSwitch, ...thumbProps } = props; + const context = useSwitchContext(THUMB_NAME, __scopeSwitch); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.span, { + "data-state": getState$1(context.checked), + "data-disabled": context.disabled ? "" : void 0, + ...thumbProps, + "ref": forwardedRef, + }); +}); +SwitchThumb.displayName = THUMB_NAME; +var BUBBLE_INPUT_NAME$1 = "SwitchBubbleInput"; +var SwitchBubbleInput = React$d.forwardRef(({ __scopeSwitch, ...props }, forwardedRef) => { + const { control, hasConsumerStoppedPropagationRef, checked, defaultChecked, required, disabled, name, value, form, bubbleInput, setBubbleInput } = useSwitchContext( + BUBBLE_INPUT_NAME$1, + __scopeSwitch, + ); + const composedRefs = useComposedRefs(forwardedRef, setBubbleInput); + const prevChecked = usePrevious$1(checked); + const controlSize = useSize(control); + React$d.useEffect(() => { + const input = bubbleInput; + if (!input) return; + const inputProto = window.HTMLInputElement.prototype; + const descriptor = Object.getOwnPropertyDescriptor(inputProto, "checked"); + const setChecked = descriptor.set; + const bubbles = !hasConsumerStoppedPropagationRef.current; + if (prevChecked !== checked && setChecked) { + const event = new Event("click", { bubbles }); + setChecked.call(input, checked); + input.dispatchEvent(event); + } + }, [bubbleInput, prevChecked, checked, hasConsumerStoppedPropagationRef]); + const defaultCheckedRef = React$d.useRef(checked); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.input, { + "type": "checkbox", + "aria-hidden": true, + "defaultChecked": defaultChecked ?? defaultCheckedRef.current, + required, + disabled, + name, + value, + form, + ...props, + "tabIndex": -1, + "ref": composedRefs, + "style": { + ...props.style, + ...controlSize, + position: "absolute", + pointerEvents: "none", + opacity: 0, + margin: 0, + // We transform because the input is absolutely positioned but we have + // rendered it **after** the button. This pulls it back to sit on top + // of the button. + transform: "translateX(-100%)", + }, + }); +}); +SwitchBubbleInput.displayName = BUBBLE_INPUT_NAME$1; +function isFunction$2(value) { + return typeof value === "function"; +} +function getState$1(checked) { + return checked ? "checked" : "unchecked"; +} + +// src/checkbox.tsx +const React$c = await importShared("react"); +var CHECKBOX_NAME = "Checkbox"; +var [createCheckboxContext] = createContextScope(CHECKBOX_NAME); +var [CheckboxProviderImpl, useCheckboxContext] = createCheckboxContext(CHECKBOX_NAME); +function CheckboxProvider(props) { + const { + __scopeCheckbox, + checked: checkedProp, + children, + defaultChecked, + disabled, + form, + name, + onCheckedChange, + required, + value = "on", + // @ts-expect-error + internal_do_not_use_render, + } = props; + const [checked, setChecked] = useControllableState({ + prop: checkedProp, + defaultProp: defaultChecked ?? false, + onChange: onCheckedChange, + caller: CHECKBOX_NAME, + }); + const [control, setControl] = React$c.useState(null); + const [bubbleInput, setBubbleInput] = React$c.useState(null); + const hasConsumerStoppedPropagationRef = React$c.useRef(false); + const isFormControl = + control ? + !!form || !!control.closest("form") + // We set this to true by default so that events bubble to forms without JS (SSR) + : true; + const context = { + checked, + disabled, + setChecked, + control, + setControl, + name, + form, + value, + hasConsumerStoppedPropagationRef, + required, + defaultChecked: isIndeterminate(defaultChecked) ? false : defaultChecked, + isFormControl, + bubbleInput, + setBubbleInput, + }; + return /* @__PURE__ */ jsxRuntimeExports.jsx(CheckboxProviderImpl, { + scope: __scopeCheckbox, + ...context, + children: isFunction$1(internal_do_not_use_render) ? internal_do_not_use_render(context) : children, + }); +} +var TRIGGER_NAME$1 = "CheckboxTrigger"; +var CheckboxTrigger = React$c.forwardRef(({ __scopeCheckbox, onKeyDown, onClick, ...checkboxProps }, forwardedRef) => { + const { control, value, disabled, checked, required, setControl, setChecked, hasConsumerStoppedPropagationRef, isFormControl, bubbleInput } = useCheckboxContext(TRIGGER_NAME$1, __scopeCheckbox); + const composedRefs = useComposedRefs(forwardedRef, setControl); + const initialCheckedStateRef = React$c.useRef(checked); + React$c.useEffect(() => { + const form = control?.form; + if (form) { + const reset = () => setChecked(initialCheckedStateRef.current); + form.addEventListener("reset", reset); + return () => form.removeEventListener("reset", reset); + } + }, [control, setChecked]); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.button, { + "type": "button", + "role": "checkbox", + "aria-checked": isIndeterminate(checked) ? "mixed" : checked, + "aria-required": required, + "data-state": getState(checked), + "data-disabled": disabled ? "" : void 0, + disabled, + value, + ...checkboxProps, + "ref": composedRefs, + "onKeyDown": composeEventHandlers(onKeyDown, (event) => { + if (event.key === "Enter") event.preventDefault(); + }), + "onClick": composeEventHandlers(onClick, (event) => { + setChecked((prevChecked) => (isIndeterminate(prevChecked) ? true : !prevChecked)); + if (bubbleInput && isFormControl) { + hasConsumerStoppedPropagationRef.current = event.isPropagationStopped(); + if (!hasConsumerStoppedPropagationRef.current) event.stopPropagation(); + } + }), + }); +}); +CheckboxTrigger.displayName = TRIGGER_NAME$1; +var Checkbox$1 = React$c.forwardRef((props, forwardedRef) => { + const { __scopeCheckbox, name, checked, defaultChecked, required, disabled, value, onCheckedChange, form, ...checkboxProps } = props; + return /* @__PURE__ */ jsxRuntimeExports.jsx(CheckboxProvider, { + __scopeCheckbox, + checked, + defaultChecked, + disabled, + required, + onCheckedChange, + name, + form, + value, + internal_do_not_use_render: ({ isFormControl }) => + /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { + children: [ + /* @__PURE__ */ jsxRuntimeExports.jsx(CheckboxTrigger, { + ...checkboxProps, + ref: forwardedRef, + __scopeCheckbox, + }), + isFormControl && + /* @__PURE__ */ jsxRuntimeExports.jsx(CheckboxBubbleInput, { + __scopeCheckbox, + }), + ], + }), + }); +}); +Checkbox$1.displayName = CHECKBOX_NAME; +var INDICATOR_NAME = "CheckboxIndicator"; +var CheckboxIndicator = React$c.forwardRef((props, forwardedRef) => { + const { __scopeCheckbox, forceMount, ...indicatorProps } = props; + const context = useCheckboxContext(INDICATOR_NAME, __scopeCheckbox); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Presence, { + present: forceMount || isIndeterminate(context.checked) || context.checked === true, + children: /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.span, { + "data-state": getState(context.checked), + "data-disabled": context.disabled ? "" : void 0, + ...indicatorProps, + "ref": forwardedRef, + "style": { pointerEvents: "none", ...props.style }, + }), + }); +}); +CheckboxIndicator.displayName = INDICATOR_NAME; +var BUBBLE_INPUT_NAME = "CheckboxBubbleInput"; +var CheckboxBubbleInput = React$c.forwardRef(({ __scopeCheckbox, ...props }, forwardedRef) => { + const { control, hasConsumerStoppedPropagationRef, checked, defaultChecked, required, disabled, name, value, form, bubbleInput, setBubbleInput } = useCheckboxContext( + BUBBLE_INPUT_NAME, + __scopeCheckbox, + ); + const composedRefs = useComposedRefs(forwardedRef, setBubbleInput); + const prevChecked = usePrevious$1(checked); + const controlSize = useSize(control); + React$c.useEffect(() => { + const input = bubbleInput; + if (!input) return; + const inputProto = window.HTMLInputElement.prototype; + const descriptor = Object.getOwnPropertyDescriptor(inputProto, "checked"); + const setChecked = descriptor.set; + const bubbles = !hasConsumerStoppedPropagationRef.current; + if (prevChecked !== checked && setChecked) { + const event = new Event("click", { bubbles }); + input.indeterminate = isIndeterminate(checked); + setChecked.call(input, isIndeterminate(checked) ? false : checked); + input.dispatchEvent(event); + } + }, [bubbleInput, prevChecked, checked, hasConsumerStoppedPropagationRef]); + const defaultCheckedRef = React$c.useRef(isIndeterminate(checked) ? false : checked); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Primitive.input, { + "type": "checkbox", + "aria-hidden": true, + "defaultChecked": defaultChecked ?? defaultCheckedRef.current, + required, + disabled, + name, + value, + form, + ...props, + "tabIndex": -1, + "ref": composedRefs, + "style": { + ...props.style, + ...controlSize, + position: "absolute", + pointerEvents: "none", + opacity: 0, + margin: 0, + // We transform because the input is absolutely positioned but we have + // rendered it **after** the button. This pulls it back to sit on top + // of the button. + transform: "translateX(-100%)", + }, + }); +}); +CheckboxBubbleInput.displayName = BUBBLE_INPUT_NAME; +function isFunction$1(value) { + return typeof value === "function"; +} +function isIndeterminate(checked) { + return checked === "indeterminate"; +} +function getState(checked) { + return ( + isIndeterminate(checked) ? "indeterminate" + : checked ? "checked" + : "unchecked" + ); +} + +// src/alert-dialog.tsx +const React$b = await importShared("react"); +var ROOT_NAME = "AlertDialog"; +var [createAlertDialogContext] = createContextScope(ROOT_NAME, [createDialogScope]); +var useDialogScope = createDialogScope(); +var AlertDialog$1 = (props) => { + const { __scopeAlertDialog, ...alertDialogProps } = props; + const dialogScope = useDialogScope(__scopeAlertDialog); + return /* @__PURE__ */ jsxRuntimeExports.jsx(Dialog$1, { ...dialogScope, ...alertDialogProps, modal: true }); +}; +AlertDialog$1.displayName = ROOT_NAME; +var TRIGGER_NAME = "AlertDialogTrigger"; +var AlertDialogTrigger$1 = React$b.forwardRef((props, forwardedRef) => { + const { __scopeAlertDialog, ...triggerProps } = props; + const dialogScope = useDialogScope(__scopeAlertDialog); + return /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTrigger$1, { ...dialogScope, ...triggerProps, ref: forwardedRef }); +}); +AlertDialogTrigger$1.displayName = TRIGGER_NAME; +var PORTAL_NAME = "AlertDialogPortal"; +var AlertDialogPortal$1 = (props) => { + const { __scopeAlertDialog, ...portalProps } = props; + const dialogScope = useDialogScope(__scopeAlertDialog); + return /* @__PURE__ */ jsxRuntimeExports.jsx(DialogPortal$1, { ...dialogScope, ...portalProps }); +}; +AlertDialogPortal$1.displayName = PORTAL_NAME; +var OVERLAY_NAME = "AlertDialogOverlay"; +var AlertDialogOverlay$1 = React$b.forwardRef((props, forwardedRef) => { + const { __scopeAlertDialog, ...overlayProps } = props; + const dialogScope = useDialogScope(__scopeAlertDialog); + return /* @__PURE__ */ jsxRuntimeExports.jsx(DialogOverlay$1, { ...dialogScope, ...overlayProps, ref: forwardedRef }); +}); +AlertDialogOverlay$1.displayName = OVERLAY_NAME; +var CONTENT_NAME = "AlertDialogContent"; +var [AlertDialogContentProvider, useAlertDialogContentContext] = createAlertDialogContext(CONTENT_NAME); +var AlertDialogContent$1 = React$b.forwardRef((props, forwardedRef) => { + const { __scopeAlertDialog, children, ...contentProps } = props; + const dialogScope = useDialogScope(__scopeAlertDialog); + const contentRef = React$b.useRef(null); + const composedRefs = useComposedRefs(forwardedRef, contentRef); + const cancelRef = React$b.useRef(null); + return /* @__PURE__ */ jsxRuntimeExports.jsx(AlertDialogContentProvider, { + scope: __scopeAlertDialog, + cancelRef, + children: /* @__PURE__ */ jsxRuntimeExports.jsx(DialogContent$1, { + role: "alertdialog", + ...dialogScope, + ...contentProps, + ref: composedRefs, + onOpenAutoFocus: composeEventHandlers(contentProps.onOpenAutoFocus, (event) => { + event.preventDefault(); + cancelRef.current?.focus({ preventScroll: true }); + }), + onPointerDownOutside: (event) => event.preventDefault(), + onInteractOutside: (event) => event.preventDefault(), + children, + }), + }); +}); +AlertDialogContent$1.displayName = CONTENT_NAME; +var TITLE_NAME = "AlertDialogTitle"; +var AlertDialogTitle$1 = React$b.forwardRef((props, forwardedRef) => { + const { __scopeAlertDialog, ...titleProps } = props; + const dialogScope = useDialogScope(__scopeAlertDialog); + return /* @__PURE__ */ jsxRuntimeExports.jsx(DialogTitle$1, { ...dialogScope, ...titleProps, ref: forwardedRef }); +}); +AlertDialogTitle$1.displayName = TITLE_NAME; +var DESCRIPTION_NAME = "AlertDialogDescription"; +var AlertDialogDescription$1 = React$b.forwardRef((props, forwardedRef) => { + const { __scopeAlertDialog, ...descriptionProps } = props; + const dialogScope = useDialogScope(__scopeAlertDialog); + return /* @__PURE__ */ jsxRuntimeExports.jsx(DialogDescription$1, { ...dialogScope, ...descriptionProps, ref: forwardedRef }); +}); +AlertDialogDescription$1.displayName = DESCRIPTION_NAME; +var ACTION_NAME = "AlertDialogAction"; +var AlertDialogAction$1 = React$b.forwardRef((props, forwardedRef) => { + const { __scopeAlertDialog, ...actionProps } = props; + const dialogScope = useDialogScope(__scopeAlertDialog); + return /* @__PURE__ */ jsxRuntimeExports.jsx(DialogClose$1, { ...dialogScope, ...actionProps, ref: forwardedRef }); +}); +AlertDialogAction$1.displayName = ACTION_NAME; +var CANCEL_NAME = "AlertDialogCancel"; +var AlertDialogCancel$1 = React$b.forwardRef((props, forwardedRef) => { + const { __scopeAlertDialog, ...cancelProps } = props; + const { cancelRef } = useAlertDialogContentContext(CANCEL_NAME, __scopeAlertDialog); + const dialogScope = useDialogScope(__scopeAlertDialog); + const ref = useComposedRefs(forwardedRef, cancelRef); + return /* @__PURE__ */ jsxRuntimeExports.jsx(DialogClose$1, { ...dialogScope, ...cancelProps, ref }); +}); +AlertDialogCancel$1.displayName = CANCEL_NAME; +var Root2 = AlertDialog$1; +var Trigger2 = AlertDialogTrigger$1; +var Portal2 = AlertDialogPortal$1; +var Overlay2 = AlertDialogOverlay$1; +var Content2 = AlertDialogContent$1; +var Action$1 = AlertDialogAction$1; +var Cancel = AlertDialogCancel$1; +var Title2 = AlertDialogTitle$1; +var Description2 = AlertDialogDescription$1; + +function __insertCSS(code) { + if (typeof document == "undefined") return; + let head = document.head || document.getElementsByTagName("head")[0]; + let style = document.createElement("style"); + style.type = "text/css"; + head.appendChild(style); + style.styleSheet ? (style.styleSheet.cssText = code) : style.appendChild(document.createTextNode(code)); +} + +const React$a = await importShared("react"); + +const ReactDOM = await importShared("react-dom"); + +const getAsset = (type) => { + switch (type) { + case "success": + return SuccessIcon; + case "info": + return InfoIcon; + case "warning": + return WarningIcon; + case "error": + return ErrorIcon; + default: + return null; + } +}; +const bars = Array(12).fill(0); +const Loader = ({ visible, className }) => { + return /*#__PURE__*/ React$a.createElement( + "div", + { + "className": ["sonner-loading-wrapper", className].filter(Boolean).join(" "), + "data-visible": visible, + }, + /*#__PURE__*/ React$a.createElement( + "div", + { + className: "sonner-spinner", + }, + bars.map((_, i) => + /*#__PURE__*/ React$a.createElement("div", { + className: "sonner-loading-bar", + key: `spinner-bar-${i}`, + }), + ), + ), + ); +}; +const SuccessIcon = /*#__PURE__*/ React$a.createElement( + "svg", + { + xmlns: "http://www.w3.org/2000/svg", + viewBox: "0 0 20 20", + fill: "currentColor", + height: "20", + width: "20", + }, + /*#__PURE__*/ React$a.createElement("path", { + fillRule: "evenodd", + d: "M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z", + clipRule: "evenodd", + }), +); +const WarningIcon = /*#__PURE__*/ React$a.createElement( + "svg", + { + xmlns: "http://www.w3.org/2000/svg", + viewBox: "0 0 24 24", + fill: "currentColor", + height: "20", + width: "20", + }, + /*#__PURE__*/ React$a.createElement("path", { + fillRule: "evenodd", + d: "M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z", + clipRule: "evenodd", + }), +); +const InfoIcon = /*#__PURE__*/ React$a.createElement( + "svg", + { + xmlns: "http://www.w3.org/2000/svg", + viewBox: "0 0 20 20", + fill: "currentColor", + height: "20", + width: "20", + }, + /*#__PURE__*/ React$a.createElement("path", { + fillRule: "evenodd", + d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z", + clipRule: "evenodd", + }), +); +const ErrorIcon = /*#__PURE__*/ React$a.createElement( + "svg", + { + xmlns: "http://www.w3.org/2000/svg", + viewBox: "0 0 20 20", + fill: "currentColor", + height: "20", + width: "20", + }, + /*#__PURE__*/ React$a.createElement("path", { + fillRule: "evenodd", + d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z", + clipRule: "evenodd", + }), +); +const CloseIcon = /*#__PURE__*/ React$a.createElement( + "svg", + { + xmlns: "http://www.w3.org/2000/svg", + width: "12", + height: "12", + viewBox: "0 0 24 24", + fill: "none", + stroke: "currentColor", + strokeWidth: "1.5", + strokeLinecap: "round", + strokeLinejoin: "round", + }, + /*#__PURE__*/ React$a.createElement("line", { + x1: "18", + y1: "6", + x2: "6", + y2: "18", + }), + /*#__PURE__*/ React$a.createElement("line", { + x1: "6", + y1: "6", + x2: "18", + y2: "18", + }), +); + +const useIsDocumentHidden = () => { + const [isDocumentHidden, setIsDocumentHidden] = React$a.useState(document.hidden); + React$a.useEffect(() => { + const callback = () => { + setIsDocumentHidden(document.hidden); + }; + document.addEventListener("visibilitychange", callback); + return () => window.removeEventListener("visibilitychange", callback); + }, []); + return isDocumentHidden; +}; + +let toastsCounter = 1; +class Observer { + constructor() { + // We use arrow functions to maintain the correct `this` reference + this.subscribe = (subscriber) => { + this.subscribers.push(subscriber); + return () => { + const index = this.subscribers.indexOf(subscriber); + this.subscribers.splice(index, 1); + }; + }; + this.publish = (data) => { + this.subscribers.forEach((subscriber) => subscriber(data)); + }; + this.addToast = (data) => { + this.publish(data); + this.toasts = [...this.toasts, data]; + }; + this.create = (data) => { + var _data_id; + const { message, ...rest } = data; + const id = typeof (data == null ? void 0 : data.id) === "number" || ((_data_id = data.id) == null ? void 0 : _data_id.length) > 0 ? data.id : toastsCounter++; + const alreadyExists = this.toasts.find((toast) => { + return toast.id === id; + }); + const dismissible = data.dismissible === undefined ? true : data.dismissible; + if (this.dismissedToasts.has(id)) { + this.dismissedToasts.delete(id); + } + if (alreadyExists) { + this.toasts = this.toasts.map((toast) => { + if (toast.id === id) { + this.publish({ + ...toast, + ...data, + id, + title: message, + }); + return { + ...toast, + ...data, + id, + dismissible, + title: message, + }; + } + return toast; + }); + } else { + this.addToast({ + title: message, + ...rest, + dismissible, + id, + }); + } + return id; + }; + this.dismiss = (id) => { + if (id) { + this.dismissedToasts.add(id); + requestAnimationFrame(() => + this.subscribers.forEach((subscriber) => + subscriber({ + id, + dismiss: true, + }), + ), + ); + } else { + this.toasts.forEach((toast) => { + this.subscribers.forEach((subscriber) => + subscriber({ + id: toast.id, + dismiss: true, + }), + ); + }); + } + return id; + }; + this.message = (message, data) => { + return this.create({ + ...data, + message, + }); + }; + this.error = (message, data) => { + return this.create({ + ...data, + message, + type: "error", + }); + }; + this.success = (message, data) => { + return this.create({ + ...data, + type: "success", + message, + }); + }; + this.info = (message, data) => { + return this.create({ + ...data, + type: "info", + message, + }); + }; + this.warning = (message, data) => { + return this.create({ + ...data, + type: "warning", + message, + }); + }; + this.loading = (message, data) => { + return this.create({ + ...data, + type: "loading", + message, + }); + }; + this.promise = (promise, data) => { + if (!data) { + // Nothing to show + return; + } + let id = undefined; + if (data.loading !== undefined) { + id = this.create({ + ...data, + promise, + type: "loading", + message: data.loading, + description: typeof data.description !== "function" ? data.description : undefined, + }); + } + const p = Promise.resolve(promise instanceof Function ? promise() : promise); + let shouldDismiss = id !== undefined; + let result; + const originalPromise = p + .then(async (response) => { + result = ["resolve", response]; + const isReactElementResponse = React$a.isValidElement(response); + if (isReactElementResponse) { + shouldDismiss = false; + this.create({ + id, + type: "default", + message: response, + }); + } else if (isHttpResponse(response) && !response.ok) { + shouldDismiss = false; + const promiseData = typeof data.error === "function" ? await data.error(`HTTP error! status: ${response.status}`) : data.error; + const description = typeof data.description === "function" ? await data.description(`HTTP error! status: ${response.status}`) : data.description; + const isExtendedResult = typeof promiseData === "object" && !React$a.isValidElement(promiseData); + const toastSettings = + isExtendedResult ? promiseData : ( + { + message: promiseData, + } + ); + this.create({ + id, + type: "error", + description, + ...toastSettings, + }); + } else if (response instanceof Error) { + shouldDismiss = false; + const promiseData = typeof data.error === "function" ? await data.error(response) : data.error; + const description = typeof data.description === "function" ? await data.description(response) : data.description; + const isExtendedResult = typeof promiseData === "object" && !React$a.isValidElement(promiseData); + const toastSettings = + isExtendedResult ? promiseData : ( + { + message: promiseData, + } + ); + this.create({ + id, + type: "error", + description, + ...toastSettings, + }); + } else if (data.success !== undefined) { + shouldDismiss = false; + const promiseData = typeof data.success === "function" ? await data.success(response) : data.success; + const description = typeof data.description === "function" ? await data.description(response) : data.description; + const isExtendedResult = typeof promiseData === "object" && !React$a.isValidElement(promiseData); + const toastSettings = + isExtendedResult ? promiseData : ( + { + message: promiseData, + } + ); + this.create({ + id, + type: "success", + description, + ...toastSettings, + }); + } + }) + .catch(async (error) => { + result = ["reject", error]; + if (data.error !== undefined) { + shouldDismiss = false; + const promiseData = typeof data.error === "function" ? await data.error(error) : data.error; + const description = typeof data.description === "function" ? await data.description(error) : data.description; + const isExtendedResult = typeof promiseData === "object" && !React$a.isValidElement(promiseData); + const toastSettings = + isExtendedResult ? promiseData : ( + { + message: promiseData, + } + ); + this.create({ + id, + type: "error", + description, + ...toastSettings, + }); + } + }) + .finally(() => { + if (shouldDismiss) { + // Toast is still in load state (and will be indefinitely — dismiss it) + this.dismiss(id); + id = undefined; + } + data.finally == null ? void 0 : data.finally.call(data); + }); + const unwrap = () => new Promise((resolve, reject) => originalPromise.then(() => (result[0] === "reject" ? reject(result[1]) : resolve(result[1]))).catch(reject)); + if (typeof id !== "string" && typeof id !== "number") { + // cannot Object.assign on undefined + return { + unwrap, + }; + } else { + return Object.assign(id, { + unwrap, + }); + } + }; + this.custom = (jsx, data) => { + const id = (data == null ? void 0 : data.id) || toastsCounter++; + this.create({ + jsx: jsx(id), + id, + ...data, + }); + return id; + }; + this.getActiveToasts = () => { + return this.toasts.filter((toast) => !this.dismissedToasts.has(toast.id)); + }; + this.subscribers = []; + this.toasts = []; + this.dismissedToasts = new Set(); + } +} +const ToastState = new Observer(); +// bind this to the toast function +const toastFunction = (message, data) => { + const id = (data == null ? void 0 : data.id) || toastsCounter++; + ToastState.addToast({ + title: message, + ...data, + id, + }); + return id; +}; +const isHttpResponse = (data) => { + return data && typeof data === "object" && "ok" in data && typeof data.ok === "boolean" && "status" in data && typeof data.status === "number"; +}; +const basicToast = toastFunction; +const getHistory = () => ToastState.toasts; +const getToasts = () => ToastState.getActiveToasts(); +// We use `Object.assign` to maintain the correct types as we would lose them otherwise +const toast = Object.assign( + basicToast, + { + success: ToastState.success, + info: ToastState.info, + warning: ToastState.warning, + error: ToastState.error, + custom: ToastState.custom, + message: ToastState.message, + promise: ToastState.promise, + dismiss: ToastState.dismiss, + loading: ToastState.loading, + }, + { + getHistory, + getToasts, + }, +); + +__insertCSS( + "[data-sonner-toaster][dir=ltr],html[dir=ltr]{--toast-icon-margin-start:-3px;--toast-icon-margin-end:4px;--toast-svg-margin-start:-1px;--toast-svg-margin-end:0px;--toast-button-margin-start:auto;--toast-button-margin-end:0;--toast-close-button-start:0;--toast-close-button-end:unset;--toast-close-button-transform:translate(-35%, -35%)}[data-sonner-toaster][dir=rtl],html[dir=rtl]{--toast-icon-margin-start:4px;--toast-icon-margin-end:-3px;--toast-svg-margin-start:0px;--toast-svg-margin-end:-1px;--toast-button-margin-start:0;--toast-button-margin-end:auto;--toast-close-button-start:unset;--toast-close-button-end:0;--toast-close-button-transform:translate(35%, -35%)}[data-sonner-toaster]{position:fixed;width:var(--width);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;--gray1:hsl(0, 0%, 99%);--gray2:hsl(0, 0%, 97.3%);--gray3:hsl(0, 0%, 95.1%);--gray4:hsl(0, 0%, 93%);--gray5:hsl(0, 0%, 90.9%);--gray6:hsl(0, 0%, 88.7%);--gray7:hsl(0, 0%, 85.8%);--gray8:hsl(0, 0%, 78%);--gray9:hsl(0, 0%, 56.1%);--gray10:hsl(0, 0%, 52.3%);--gray11:hsl(0, 0%, 43.5%);--gray12:hsl(0, 0%, 9%);--border-radius:8px;box-sizing:border-box;padding:0;margin:0;list-style:none;outline:0;z-index:999999999;transition:transform .4s ease}@media (hover:none) and (pointer:coarse){[data-sonner-toaster][data-lifted=true]{transform:none}}[data-sonner-toaster][data-x-position=right]{right:var(--offset-right)}[data-sonner-toaster][data-x-position=left]{left:var(--offset-left)}[data-sonner-toaster][data-x-position=center]{left:50%;transform:translateX(-50%)}[data-sonner-toaster][data-y-position=top]{top:var(--offset-top)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--offset-bottom)}[data-sonner-toast]{--y:translateY(100%);--lift-amount:calc(var(--lift) * var(--gap));z-index:var(--z-index);position:absolute;opacity:0;transform:var(--y);touch-action:none;transition:transform .4s,opacity .4s,height .4s,box-shadow .2s;box-sizing:border-box;outline:0;overflow-wrap:anywhere}[data-sonner-toast][data-styled=true]{padding:16px;background:var(--normal-bg);border:1px solid var(--normal-border);color:var(--normal-text);border-radius:var(--border-radius);box-shadow:0 4px 12px rgba(0,0,0,.1);width:var(--width);font-size:13px;display:flex;align-items:center;gap:6px}[data-sonner-toast]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-y-position=top]{top:0;--y:translateY(-100%);--lift:1;--lift-amount:calc(1 * var(--gap))}[data-sonner-toast][data-y-position=bottom]{bottom:0;--y:translateY(100%);--lift:-1;--lift-amount:calc(var(--lift) * var(--gap))}[data-sonner-toast][data-styled=true] [data-description]{font-weight:400;line-height:1.4;color:#3f3f3f}[data-rich-colors=true][data-sonner-toast][data-styled=true] [data-description]{color:inherit}[data-sonner-toaster][data-sonner-theme=dark] [data-description]{color:#e8e8e8}[data-sonner-toast][data-styled=true] [data-title]{font-weight:500;line-height:1.5;color:inherit}[data-sonner-toast][data-styled=true] [data-icon]{display:flex;height:16px;width:16px;position:relative;justify-content:flex-start;align-items:center;flex-shrink:0;margin-left:var(--toast-icon-margin-start);margin-right:var(--toast-icon-margin-end)}[data-sonner-toast][data-promise=true] [data-icon]>svg{opacity:0;transform:scale(.8);transform-origin:center;animation:sonner-fade-in .3s ease forwards}[data-sonner-toast][data-styled=true] [data-icon]>*{flex-shrink:0}[data-sonner-toast][data-styled=true] [data-icon] svg{margin-left:var(--toast-svg-margin-start);margin-right:var(--toast-svg-margin-end)}[data-sonner-toast][data-styled=true] [data-content]{display:flex;flex-direction:column;gap:2px}[data-sonner-toast][data-styled=true] [data-button]{border-radius:4px;padding-left:8px;padding-right:8px;height:24px;font-size:12px;color:var(--normal-bg);background:var(--normal-text);margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end);border:none;font-weight:500;cursor:pointer;outline:0;display:flex;align-items:center;flex-shrink:0;transition:opacity .4s,box-shadow .2s}[data-sonner-toast][data-styled=true] [data-button]:focus-visible{box-shadow:0 0 0 2px rgba(0,0,0,.4)}[data-sonner-toast][data-styled=true] [data-button]:first-of-type{margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end)}[data-sonner-toast][data-styled=true] [data-cancel]{color:var(--normal-text);background:rgba(0,0,0,.08)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-styled=true] [data-cancel]{background:rgba(255,255,255,.3)}[data-sonner-toast][data-styled=true] [data-close-button]{position:absolute;left:var(--toast-close-button-start);right:var(--toast-close-button-end);top:0;height:20px;width:20px;display:flex;justify-content:center;align-items:center;padding:0;color:var(--gray12);background:var(--normal-bg);border:1px solid var(--gray4);transform:var(--toast-close-button-transform);border-radius:50%;cursor:pointer;z-index:1;transition:opacity .1s,background .2s,border-color .2s}[data-sonner-toast][data-styled=true] [data-close-button]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-styled=true] [data-disabled=true]{cursor:not-allowed}[data-sonner-toast][data-styled=true]:hover [data-close-button]:hover{background:var(--gray2);border-color:var(--gray5)}[data-sonner-toast][data-swiping=true]::before{content:'';position:absolute;left:-100%;right:-100%;height:100%;z-index:-1}[data-sonner-toast][data-y-position=top][data-swiping=true]::before{bottom:50%;transform:scaleY(3) translateY(50%)}[data-sonner-toast][data-y-position=bottom][data-swiping=true]::before{top:50%;transform:scaleY(3) translateY(-50%)}[data-sonner-toast][data-swiping=false][data-removed=true]::before{content:'';position:absolute;inset:0;transform:scaleY(2)}[data-sonner-toast][data-expanded=true]::after{content:'';position:absolute;left:0;height:calc(var(--gap) + 1px);bottom:100%;width:100%}[data-sonner-toast][data-mounted=true]{--y:translateY(0);opacity:1}[data-sonner-toast][data-expanded=false][data-front=false]{--scale:var(--toasts-before) * 0.05 + 1;--y:translateY(calc(var(--lift-amount) * var(--toasts-before))) scale(calc(-1 * var(--scale)));height:var(--front-toast-height)}[data-sonner-toast]>*{transition:opacity .4s}[data-sonner-toast][data-x-position=right]{right:0}[data-sonner-toast][data-x-position=left]{left:0}[data-sonner-toast][data-expanded=false][data-front=false][data-styled=true]>*{opacity:0}[data-sonner-toast][data-visible=false]{opacity:0;pointer-events:none}[data-sonner-toast][data-mounted=true][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset)));height:var(--initial-height)}[data-sonner-toast][data-removed=true][data-front=true][data-swipe-out=false]{--y:translateY(calc(var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset) + var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=false]{--y:translateY(40%);opacity:0;transition:transform .5s,opacity .2s}[data-sonner-toast][data-removed=true][data-front=false]::before{height:calc(var(--initial-height) + 20%)}[data-sonner-toast][data-swiping=true]{transform:var(--y) translateY(var(--swipe-amount-y,0)) translateX(var(--swipe-amount-x,0));transition:none}[data-sonner-toast][data-swiped=true]{user-select:none}[data-sonner-toast][data-swipe-out=true][data-y-position=bottom],[data-sonner-toast][data-swipe-out=true][data-y-position=top]{animation-duration:.2s;animation-timing-function:ease-out;animation-fill-mode:forwards}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=left]{animation-name:swipe-out-left}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=right]{animation-name:swipe-out-right}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=up]{animation-name:swipe-out-up}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=down]{animation-name:swipe-out-down}@keyframes swipe-out-left{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) - 100%));opacity:0}}@keyframes swipe-out-right{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) + 100%));opacity:0}}@keyframes swipe-out-up{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) - 100%));opacity:0}}@keyframes swipe-out-down{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) + 100%));opacity:0}}@media (max-width:600px){[data-sonner-toaster]{position:fixed;right:var(--mobile-offset-right);left:var(--mobile-offset-left);width:100%}[data-sonner-toaster][dir=rtl]{left:calc(var(--mobile-offset-left) * -1)}[data-sonner-toaster] [data-sonner-toast]{left:0;right:0;width:calc(100% - var(--mobile-offset-left) * 2)}[data-sonner-toaster][data-x-position=left]{left:var(--mobile-offset-left)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--mobile-offset-bottom)}[data-sonner-toaster][data-y-position=top]{top:var(--mobile-offset-top)}[data-sonner-toaster][data-x-position=center]{left:var(--mobile-offset-left);right:var(--mobile-offset-right);transform:none}}[data-sonner-toaster][data-sonner-theme=light]{--normal-bg:#fff;--normal-border:var(--gray4);--normal-text:var(--gray12);--success-bg:hsl(143, 85%, 96%);--success-border:hsl(145, 92%, 87%);--success-text:hsl(140, 100%, 27%);--info-bg:hsl(208, 100%, 97%);--info-border:hsl(221, 91%, 93%);--info-text:hsl(210, 92%, 45%);--warning-bg:hsl(49, 100%, 97%);--warning-border:hsl(49, 91%, 84%);--warning-text:hsl(31, 92%, 45%);--error-bg:hsl(359, 100%, 97%);--error-border:hsl(359, 100%, 94%);--error-text:hsl(360, 100%, 45%)}[data-sonner-toaster][data-sonner-theme=light] [data-sonner-toast][data-invert=true]{--normal-bg:#000;--normal-border:hsl(0, 0%, 20%);--normal-text:var(--gray1)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-invert=true]{--normal-bg:#fff;--normal-border:var(--gray3);--normal-text:var(--gray12)}[data-sonner-toaster][data-sonner-theme=dark]{--normal-bg:#000;--normal-bg-hover:hsl(0, 0%, 12%);--normal-border:hsl(0, 0%, 20%);--normal-border-hover:hsl(0, 0%, 25%);--normal-text:var(--gray1);--success-bg:hsl(150, 100%, 6%);--success-border:hsl(147, 100%, 12%);--success-text:hsl(150, 86%, 65%);--info-bg:hsl(215, 100%, 6%);--info-border:hsl(223, 43%, 17%);--info-text:hsl(216, 87%, 65%);--warning-bg:hsl(64, 100%, 6%);--warning-border:hsl(60, 100%, 9%);--warning-text:hsl(46, 87%, 65%);--error-bg:hsl(358, 76%, 10%);--error-border:hsl(357, 89%, 16%);--error-text:hsl(358, 100%, 81%)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]{background:var(--normal-bg);border-color:var(--normal-border);color:var(--normal-text)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]:hover{background:var(--normal-bg-hover);border-color:var(--normal-border-hover)}[data-rich-colors=true][data-sonner-toast][data-type=success]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=success] [data-close-button]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=info]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=info] [data-close-button]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning] [data-close-button]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=error]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}[data-rich-colors=true][data-sonner-toast][data-type=error] [data-close-button]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}.sonner-loading-wrapper{--size:16px;height:var(--size);width:var(--size);position:absolute;inset:0;z-index:10}.sonner-loading-wrapper[data-visible=false]{transform-origin:center;animation:sonner-fade-out .2s ease forwards}.sonner-spinner{position:relative;top:50%;left:50%;height:var(--size);width:var(--size)}.sonner-loading-bar{animation:sonner-spin 1.2s linear infinite;background:var(--gray11);border-radius:6px;height:8%;left:-10%;position:absolute;top:-3.9%;width:24%}.sonner-loading-bar:first-child{animation-delay:-1.2s;transform:rotate(.0001deg) translate(146%)}.sonner-loading-bar:nth-child(2){animation-delay:-1.1s;transform:rotate(30deg) translate(146%)}.sonner-loading-bar:nth-child(3){animation-delay:-1s;transform:rotate(60deg) translate(146%)}.sonner-loading-bar:nth-child(4){animation-delay:-.9s;transform:rotate(90deg) translate(146%)}.sonner-loading-bar:nth-child(5){animation-delay:-.8s;transform:rotate(120deg) translate(146%)}.sonner-loading-bar:nth-child(6){animation-delay:-.7s;transform:rotate(150deg) translate(146%)}.sonner-loading-bar:nth-child(7){animation-delay:-.6s;transform:rotate(180deg) translate(146%)}.sonner-loading-bar:nth-child(8){animation-delay:-.5s;transform:rotate(210deg) translate(146%)}.sonner-loading-bar:nth-child(9){animation-delay:-.4s;transform:rotate(240deg) translate(146%)}.sonner-loading-bar:nth-child(10){animation-delay:-.3s;transform:rotate(270deg) translate(146%)}.sonner-loading-bar:nth-child(11){animation-delay:-.2s;transform:rotate(300deg) translate(146%)}.sonner-loading-bar:nth-child(12){animation-delay:-.1s;transform:rotate(330deg) translate(146%)}@keyframes sonner-fade-in{0%{opacity:0;transform:scale(.8)}100%{opacity:1;transform:scale(1)}}@keyframes sonner-fade-out{0%{opacity:1;transform:scale(1)}100%{opacity:0;transform:scale(.8)}}@keyframes sonner-spin{0%{opacity:1}100%{opacity:.15}}@media (prefers-reduced-motion){.sonner-loading-bar,[data-sonner-toast],[data-sonner-toast]>*{transition:none!important;animation:none!important}}.sonner-loader{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);transform-origin:center;transition:opacity .2s,transform .2s}.sonner-loader[data-visible=false]{opacity:0;transform:scale(.8) translate(-50%,-50%)}", +); + +function isAction(action) { + return action.label !== undefined; +} + +// Visible toasts amount +const VISIBLE_TOASTS_AMOUNT = 3; +// Viewport padding +const VIEWPORT_OFFSET = "24px"; +// Mobile viewport padding +const MOBILE_VIEWPORT_OFFSET = "16px"; +// Default lifetime of a toasts (in ms) +const TOAST_LIFETIME = 4000; +// Default toast width +const TOAST_WIDTH = 356; +// Default gap between toasts +const GAP = 14; +// Threshold to dismiss a toast +const SWIPE_THRESHOLD = 45; +// Equal to exit animation duration +const TIME_BEFORE_UNMOUNT = 200; +function cn$1(...classes) { + return classes.filter(Boolean).join(" "); +} +function getDefaultSwipeDirections(position) { + const [y, x] = position.split("-"); + const directions = []; + if (y) { + directions.push(y); + } + if (x) { + directions.push(x); + } + return directions; +} +const Toast = (props) => { + var _toast_classNames, _toast_classNames1, _toast_classNames2, _toast_classNames3, _toast_classNames4, _toast_classNames5, _toast_classNames6, _toast_classNames7, _toast_classNames8; + const { + invert: ToasterInvert, + toast, + unstyled, + interacting, + setHeights, + visibleToasts, + heights, + index, + toasts, + expanded, + removeToast, + defaultRichColors, + closeButton: closeButtonFromToaster, + style, + cancelButtonStyle, + actionButtonStyle, + className = "", + descriptionClassName = "", + duration: durationFromToaster, + position, + gap, + expandByDefault, + classNames, + icons, + closeButtonAriaLabel = "Close toast", + } = props; + const [swipeDirection, setSwipeDirection] = React$a.useState(null); + const [swipeOutDirection, setSwipeOutDirection] = React$a.useState(null); + const [mounted, setMounted] = React$a.useState(false); + const [removed, setRemoved] = React$a.useState(false); + const [swiping, setSwiping] = React$a.useState(false); + const [swipeOut, setSwipeOut] = React$a.useState(false); + const [isSwiped, setIsSwiped] = React$a.useState(false); + const [offsetBeforeRemove, setOffsetBeforeRemove] = React$a.useState(0); + const [initialHeight, setInitialHeight] = React$a.useState(0); + const remainingTime = React$a.useRef(toast.duration || durationFromToaster || TOAST_LIFETIME); + const dragStartTime = React$a.useRef(null); + const toastRef = React$a.useRef(null); + const isFront = index === 0; + const isVisible = index + 1 <= visibleToasts; + const toastType = toast.type; + const dismissible = toast.dismissible !== false; + const toastClassname = toast.className || ""; + const toastDescriptionClassname = toast.descriptionClassName || ""; + // Height index is used to calculate the offset as it gets updated before the toast array, which means we can calculate the new layout faster. + const heightIndex = React$a.useMemo(() => heights.findIndex((height) => height.toastId === toast.id) || 0, [heights, toast.id]); + const closeButton = React$a.useMemo(() => { + var _toast_closeButton; + return (_toast_closeButton = toast.closeButton) != null ? _toast_closeButton : closeButtonFromToaster; + }, [toast.closeButton, closeButtonFromToaster]); + const duration = React$a.useMemo(() => toast.duration || durationFromToaster || TOAST_LIFETIME, [toast.duration, durationFromToaster]); + const closeTimerStartTimeRef = React$a.useRef(0); + const offset = React$a.useRef(0); + const lastCloseTimerStartTimeRef = React$a.useRef(0); + const pointerStartRef = React$a.useRef(null); + const [y, x] = position.split("-"); + const toastsHeightBefore = React$a.useMemo(() => { + return heights.reduce((prev, curr, reducerIndex) => { + // Calculate offset up until current toast + if (reducerIndex >= heightIndex) { + return prev; + } + return prev + curr.height; + }, 0); + }, [heights, heightIndex]); + const isDocumentHidden = useIsDocumentHidden(); + const invert = toast.invert || ToasterInvert; + const disabled = toastType === "loading"; + offset.current = React$a.useMemo(() => heightIndex * gap + toastsHeightBefore, [heightIndex, toastsHeightBefore]); + React$a.useEffect(() => { + remainingTime.current = duration; + }, [duration]); + React$a.useEffect(() => { + // Trigger enter animation without using CSS animation + setMounted(true); + }, []); + React$a.useEffect(() => { + const toastNode = toastRef.current; + if (toastNode) { + const height = toastNode.getBoundingClientRect().height; + // Add toast height to heights array after the toast is mounted + setInitialHeight(height); + setHeights((h) => [ + { + toastId: toast.id, + height, + position: toast.position, + }, + ...h, + ]); + return () => setHeights((h) => h.filter((height) => height.toastId !== toast.id)); + } + }, [setHeights, toast.id]); + React$a.useLayoutEffect(() => { + // Keep height up to date with the content in case it updates + if (!mounted) return; + const toastNode = toastRef.current; + const originalHeight = toastNode.style.height; + toastNode.style.height = "auto"; + const newHeight = toastNode.getBoundingClientRect().height; + toastNode.style.height = originalHeight; + setInitialHeight(newHeight); + setHeights((heights) => { + const alreadyExists = heights.find((height) => height.toastId === toast.id); + if (!alreadyExists) { + return [ + { + toastId: toast.id, + height: newHeight, + position: toast.position, + }, + ...heights, + ]; + } else { + return heights.map((height) => + height.toastId === toast.id ? + { + ...height, + height: newHeight, + } + : height, + ); + } + }); + }, [mounted, toast.title, toast.description, setHeights, toast.id, toast.jsx, toast.action, toast.cancel]); + const deleteToast = React$a.useCallback(() => { + // Save the offset for the exit swipe animation + setRemoved(true); + setOffsetBeforeRemove(offset.current); + setHeights((h) => h.filter((height) => height.toastId !== toast.id)); + setTimeout(() => { + removeToast(toast); + }, TIME_BEFORE_UNMOUNT); + }, [toast, removeToast, setHeights, offset]); + React$a.useEffect(() => { + if ((toast.promise && toastType === "loading") || toast.duration === Infinity || toast.type === "loading") return; + let timeoutId; + // Pause the timer on each hover + const pauseTimer = () => { + if (lastCloseTimerStartTimeRef.current < closeTimerStartTimeRef.current) { + // Get the elapsed time since the timer started + const elapsedTime = new Date().getTime() - closeTimerStartTimeRef.current; + remainingTime.current = remainingTime.current - elapsedTime; + } + lastCloseTimerStartTimeRef.current = new Date().getTime(); + }; + const startTimer = () => { + // setTimeout(, Infinity) behaves as if the delay is 0. + // As a result, the toast would be closed immediately, giving the appearance that it was never rendered. + // See: https://github.com/denysdovhan/wtfjs?tab=readme-ov-file#an-infinite-timeout + if (remainingTime.current === Infinity) return; + closeTimerStartTimeRef.current = new Date().getTime(); + // Let the toast know it has started + timeoutId = setTimeout(() => { + toast.onAutoClose == null ? void 0 : toast.onAutoClose.call(toast, toast); + deleteToast(); + }, remainingTime.current); + }; + if (expanded || interacting || isDocumentHidden) { + pauseTimer(); + } else { + startTimer(); + } + return () => clearTimeout(timeoutId); + }, [expanded, interacting, toast, toastType, isDocumentHidden, deleteToast]); + React$a.useEffect(() => { + if (toast.delete) { + deleteToast(); + toast.onDismiss == null ? void 0 : toast.onDismiss.call(toast, toast); + } + }, [deleteToast, toast.delete]); + function getLoadingIcon() { + var _toast_classNames; + if (icons == null ? void 0 : icons.loading) { + var _toast_classNames1; + return /*#__PURE__*/ React$a.createElement( + "div", + { + "className": cn$1( + classNames == null ? void 0 : classNames.loader, + toast == null ? void 0 + : (_toast_classNames1 = toast.classNames) == null ? void 0 + : _toast_classNames1.loader, + "sonner-loader", + ), + "data-visible": toastType === "loading", + }, + icons.loading, + ); + } + return /*#__PURE__*/ React$a.createElement(Loader, { + className: cn$1( + classNames == null ? void 0 : classNames.loader, + toast == null ? void 0 + : (_toast_classNames = toast.classNames) == null ? void 0 + : _toast_classNames.loader, + ), + visible: toastType === "loading", + }); + } + const icon = toast.icon || (icons == null ? void 0 : icons[toastType]) || getAsset(toastType); + var _toast_richColors, _icons_close; + return /*#__PURE__*/ React$a.createElement( + "li", + { + "tabIndex": 0, + "ref": toastRef, + "className": cn$1( + className, + toastClassname, + classNames == null ? void 0 : classNames.toast, + toast == null ? void 0 + : (_toast_classNames = toast.classNames) == null ? void 0 + : _toast_classNames.toast, + classNames == null ? void 0 : classNames.default, + classNames == null ? void 0 : classNames[toastType], + toast == null ? void 0 + : (_toast_classNames1 = toast.classNames) == null ? void 0 + : _toast_classNames1[toastType], + ), + "data-sonner-toast": "", + "data-rich-colors": (_toast_richColors = toast.richColors) != null ? _toast_richColors : defaultRichColors, + "data-styled": !Boolean(toast.jsx || toast.unstyled || unstyled), + "data-mounted": mounted, + "data-promise": Boolean(toast.promise), + "data-swiped": isSwiped, + "data-removed": removed, + "data-visible": isVisible, + "data-y-position": y, + "data-x-position": x, + "data-index": index, + "data-front": isFront, + "data-swiping": swiping, + "data-dismissible": dismissible, + "data-type": toastType, + "data-invert": invert, + "data-swipe-out": swipeOut, + "data-swipe-direction": swipeOutDirection, + "data-expanded": Boolean(expanded || (expandByDefault && mounted)), + "data-testid": toast.testId, + "style": { + "--index": index, + "--toasts-before": index, + "--z-index": toasts.length - index, + "--offset": `${removed ? offsetBeforeRemove : offset.current}px`, + "--initial-height": expandByDefault ? "auto" : `${initialHeight}px`, + ...style, + ...toast.style, + }, + "onDragEnd": () => { + setSwiping(false); + setSwipeDirection(null); + pointerStartRef.current = null; + }, + "onPointerDown": (event) => { + if (event.button === 2) return; // Return early on right click + if (disabled || !dismissible) return; + dragStartTime.current = new Date(); + setOffsetBeforeRemove(offset.current); + // Ensure we maintain correct pointer capture even when going outside of the toast (e.g. when swiping) + event.target.setPointerCapture(event.pointerId); + if (event.target.tagName === "BUTTON") return; + setSwiping(true); + pointerStartRef.current = { + x: event.clientX, + y: event.clientY, + }; + }, + "onPointerUp": () => { + var _toastRef_current, _toastRef_current1, _dragStartTime_current; + if (swipeOut || !dismissible) return; + pointerStartRef.current = null; + const swipeAmountX = Number(((_toastRef_current = toastRef.current) == null ? void 0 : _toastRef_current.style.getPropertyValue("--swipe-amount-x").replace("px", "")) || 0); + const swipeAmountY = Number(((_toastRef_current1 = toastRef.current) == null ? void 0 : _toastRef_current1.style.getPropertyValue("--swipe-amount-y").replace("px", "")) || 0); + const timeTaken = new Date().getTime() - ((_dragStartTime_current = dragStartTime.current) == null ? void 0 : _dragStartTime_current.getTime()); + const swipeAmount = swipeDirection === "x" ? swipeAmountX : swipeAmountY; + const velocity = Math.abs(swipeAmount) / timeTaken; + if (Math.abs(swipeAmount) >= SWIPE_THRESHOLD || velocity > 0.11) { + setOffsetBeforeRemove(offset.current); + toast.onDismiss == null ? void 0 : toast.onDismiss.call(toast, toast); + if (swipeDirection === "x") { + setSwipeOutDirection(swipeAmountX > 0 ? "right" : "left"); + } else { + setSwipeOutDirection(swipeAmountY > 0 ? "down" : "up"); + } + deleteToast(); + setSwipeOut(true); + return; + } else { + var _toastRef_current2, _toastRef_current3; + (_toastRef_current2 = toastRef.current) == null ? void 0 : _toastRef_current2.style.setProperty("--swipe-amount-x", `0px`); + (_toastRef_current3 = toastRef.current) == null ? void 0 : _toastRef_current3.style.setProperty("--swipe-amount-y", `0px`); + } + setIsSwiped(false); + setSwiping(false); + setSwipeDirection(null); + }, + "onPointerMove": (event) => { + var _window_getSelection, // Apply transform using both x and y values + _toastRef_current, + _toastRef_current1; + if (!pointerStartRef.current || !dismissible) return; + const isHighlighted = ((_window_getSelection = window.getSelection()) == null ? void 0 : _window_getSelection.toString().length) > 0; + if (isHighlighted) return; + const yDelta = event.clientY - pointerStartRef.current.y; + const xDelta = event.clientX - pointerStartRef.current.x; + var _props_swipeDirections; + const swipeDirections = (_props_swipeDirections = props.swipeDirections) != null ? _props_swipeDirections : getDefaultSwipeDirections(position); + // Determine swipe direction if not already locked + if (!swipeDirection && (Math.abs(xDelta) > 1 || Math.abs(yDelta) > 1)) { + setSwipeDirection(Math.abs(xDelta) > Math.abs(yDelta) ? "x" : "y"); + } + let swipeAmount = { + x: 0, + y: 0, + }; + const getDampening = (delta) => { + const factor = Math.abs(delta) / 20; + return 1 / (1.5 + factor); + }; + // Only apply swipe in the locked direction + if (swipeDirection === "y") { + // Handle vertical swipes + if (swipeDirections.includes("top") || swipeDirections.includes("bottom")) { + if ((swipeDirections.includes("top") && yDelta < 0) || (swipeDirections.includes("bottom") && yDelta > 0)) { + swipeAmount.y = yDelta; + } else { + // Smoothly transition to dampened movement + const dampenedDelta = yDelta * getDampening(yDelta); + // Ensure we don't jump when transitioning to dampened movement + swipeAmount.y = Math.abs(dampenedDelta) < Math.abs(yDelta) ? dampenedDelta : yDelta; + } + } + } else if (swipeDirection === "x") { + // Handle horizontal swipes + if (swipeDirections.includes("left") || swipeDirections.includes("right")) { + if ((swipeDirections.includes("left") && xDelta < 0) || (swipeDirections.includes("right") && xDelta > 0)) { + swipeAmount.x = xDelta; + } else { + // Smoothly transition to dampened movement + const dampenedDelta = xDelta * getDampening(xDelta); + // Ensure we don't jump when transitioning to dampened movement + swipeAmount.x = Math.abs(dampenedDelta) < Math.abs(xDelta) ? dampenedDelta : xDelta; + } + } + } + if (Math.abs(swipeAmount.x) > 0 || Math.abs(swipeAmount.y) > 0) { + setIsSwiped(true); + } + (_toastRef_current = toastRef.current) == null ? void 0 : _toastRef_current.style.setProperty("--swipe-amount-x", `${swipeAmount.x}px`); + (_toastRef_current1 = toastRef.current) == null ? void 0 : _toastRef_current1.style.setProperty("--swipe-amount-y", `${swipeAmount.y}px`); + }, + }, + closeButton && !toast.jsx && toastType !== "loading" ? + /*#__PURE__*/ React$a.createElement( + "button", + { + "aria-label": closeButtonAriaLabel, + "data-disabled": disabled, + "data-close-button": true, + "onClick": + disabled || !dismissible ? + () => {} + : () => { + deleteToast(); + toast.onDismiss == null ? void 0 : toast.onDismiss.call(toast, toast); + }, + "className": cn$1( + classNames == null ? void 0 : classNames.closeButton, + toast == null ? void 0 + : (_toast_classNames2 = toast.classNames) == null ? void 0 + : _toast_classNames2.closeButton, + ), + }, + (_icons_close = icons == null ? void 0 : icons.close) != null ? _icons_close : CloseIcon, + ) + : null, + (toastType || toast.icon || toast.promise) && toast.icon !== null && ((icons == null ? void 0 : icons[toastType]) !== null || toast.icon) ? + /*#__PURE__*/ React$a.createElement( + "div", + { + "data-icon": "", + "className": cn$1( + classNames == null ? void 0 : classNames.icon, + toast == null ? void 0 + : (_toast_classNames3 = toast.classNames) == null ? void 0 + : _toast_classNames3.icon, + ), + }, + toast.promise || (toast.type === "loading" && !toast.icon) ? toast.icon || getLoadingIcon() : null, + toast.type !== "loading" ? icon : null, + ) + : null, + /*#__PURE__*/ React$a.createElement( + "div", + { + "data-content": "", + "className": cn$1( + classNames == null ? void 0 : classNames.content, + toast == null ? void 0 + : (_toast_classNames4 = toast.classNames) == null ? void 0 + : _toast_classNames4.content, + ), + }, + /*#__PURE__*/ React$a.createElement( + "div", + { + "data-title": "", + "className": cn$1( + classNames == null ? void 0 : classNames.title, + toast == null ? void 0 + : (_toast_classNames5 = toast.classNames) == null ? void 0 + : _toast_classNames5.title, + ), + }, + toast.jsx ? toast.jsx + : typeof toast.title === "function" ? toast.title() + : toast.title, + ), + toast.description ? + /*#__PURE__*/ React$a.createElement( + "div", + { + "data-description": "", + "className": cn$1( + descriptionClassName, + toastDescriptionClassname, + classNames == null ? void 0 : classNames.description, + toast == null ? void 0 + : (_toast_classNames6 = toast.classNames) == null ? void 0 + : _toast_classNames6.description, + ), + }, + typeof toast.description === "function" ? toast.description() : toast.description, + ) + : null, + ), + /*#__PURE__*/ React$a.isValidElement(toast.cancel) ? toast.cancel + : toast.cancel && isAction(toast.cancel) ? + /*#__PURE__*/ React$a.createElement( + "button", + { + "data-button": true, + "data-cancel": true, + "style": toast.cancelButtonStyle || cancelButtonStyle, + "onClick": (event) => { + // We need to check twice because typescript + if (!isAction(toast.cancel)) return; + if (!dismissible) return; + toast.cancel.onClick == null ? void 0 : toast.cancel.onClick.call(toast.cancel, event); + deleteToast(); + }, + "className": cn$1( + classNames == null ? void 0 : classNames.cancelButton, + toast == null ? void 0 + : (_toast_classNames7 = toast.classNames) == null ? void 0 + : _toast_classNames7.cancelButton, + ), + }, + toast.cancel.label, + ) + : null, + /*#__PURE__*/ React$a.isValidElement(toast.action) ? toast.action + : toast.action && isAction(toast.action) ? + /*#__PURE__*/ React$a.createElement( + "button", + { + "data-button": true, + "data-action": true, + "style": toast.actionButtonStyle || actionButtonStyle, + "onClick": (event) => { + // We need to check twice because typescript + if (!isAction(toast.action)) return; + toast.action.onClick == null ? void 0 : toast.action.onClick.call(toast.action, event); + if (event.defaultPrevented) return; + deleteToast(); + }, + "className": cn$1( + classNames == null ? void 0 : classNames.actionButton, + toast == null ? void 0 + : (_toast_classNames8 = toast.classNames) == null ? void 0 + : _toast_classNames8.actionButton, + ), + }, + toast.action.label, + ) + : null, + ); +}; +function getDocumentDirection() { + if (typeof window === "undefined") return "ltr"; + if (typeof document === "undefined") return "ltr"; // For Fresh purpose + const dirAttribute = document.documentElement.getAttribute("dir"); + if (dirAttribute === "auto" || !dirAttribute) { + return window.getComputedStyle(document.documentElement).direction; + } + return dirAttribute; +} +function assignOffset(defaultOffset, mobileOffset) { + const styles = {}; + [defaultOffset, mobileOffset].forEach((offset, index) => { + const isMobile = index === 1; + const prefix = isMobile ? "--mobile-offset" : "--offset"; + const defaultValue = isMobile ? MOBILE_VIEWPORT_OFFSET : VIEWPORT_OFFSET; + function assignAll(offset) { + ["top", "right", "bottom", "left"].forEach((key) => { + styles[`${prefix}-${key}`] = typeof offset === "number" ? `${offset}px` : offset; + }); + } + if (typeof offset === "number" || typeof offset === "string") { + assignAll(offset); + } else if (typeof offset === "object") { + ["top", "right", "bottom", "left"].forEach((key) => { + if (offset[key] === undefined) { + styles[`${prefix}-${key}`] = defaultValue; + } else { + styles[`${prefix}-${key}`] = typeof offset[key] === "number" ? `${offset[key]}px` : offset[key]; + } + }); + } else { + assignAll(defaultValue); + } + }); + return styles; +} +const Toaster$1 = /*#__PURE__*/ React$a.forwardRef(function Toaster(props, ref) { + const { + id, + invert, + position = "bottom-right", + hotkey = ["altKey", "KeyT"], + expand, + closeButton, + className, + offset, + mobileOffset, + theme = "light", + richColors, + duration, + style, + visibleToasts = VISIBLE_TOASTS_AMOUNT, + toastOptions, + dir = getDocumentDirection(), + gap = GAP, + icons, + containerAriaLabel = "Notifications", + } = props; + const [toasts, setToasts] = React$a.useState([]); + const filteredToasts = React$a.useMemo(() => { + if (id) { + return toasts.filter((toast) => toast.toasterId === id); + } + return toasts.filter((toast) => !toast.toasterId); + }, [toasts, id]); + const possiblePositions = React$a.useMemo(() => { + return Array.from(new Set([position].concat(filteredToasts.filter((toast) => toast.position).map((toast) => toast.position)))); + }, [filteredToasts, position]); + const [heights, setHeights] = React$a.useState([]); + const [expanded, setExpanded] = React$a.useState(false); + const [interacting, setInteracting] = React$a.useState(false); + const [actualTheme, setActualTheme] = React$a.useState( + theme !== "system" ? theme + : typeof window !== "undefined" ? + window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? + "dark" + : "light" + : "light", + ); + const listRef = React$a.useRef(null); + const hotkeyLabel = hotkey.join("+").replace(/Key/g, "").replace(/Digit/g, ""); + const lastFocusedElementRef = React$a.useRef(null); + const isFocusWithinRef = React$a.useRef(false); + const removeToast = React$a.useCallback((toastToRemove) => { + setToasts((toasts) => { + var _toasts_find; + if (!((_toasts_find = toasts.find((toast) => toast.id === toastToRemove.id)) == null ? void 0 : _toasts_find.delete)) { + ToastState.dismiss(toastToRemove.id); + } + return toasts.filter(({ id }) => id !== toastToRemove.id); + }); + }, []); + React$a.useEffect(() => { + return ToastState.subscribe((toast) => { + if (toast.dismiss) { + // Prevent batching of other state updates + requestAnimationFrame(() => { + setToasts((toasts) => + toasts.map((t) => + t.id === toast.id ? + { + ...t, + delete: true, + } + : t, + ), + ); + }); + return; + } + // Prevent batching, temp solution. + setTimeout(() => { + ReactDOM.flushSync(() => { + setToasts((toasts) => { + const indexOfExistingToast = toasts.findIndex((t) => t.id === toast.id); + // Update the toast if it already exists + if (indexOfExistingToast !== -1) { + return [ + ...toasts.slice(0, indexOfExistingToast), + { + ...toasts[indexOfExistingToast], + ...toast, + }, + ...toasts.slice(indexOfExistingToast + 1), + ]; + } + return [toast, ...toasts]; + }); + }); + }); + }); + }, [toasts]); + React$a.useEffect(() => { + if (theme !== "system") { + setActualTheme(theme); + return; + } + if (theme === "system") { + // check if current preference is dark + if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) { + // it's currently dark + setActualTheme("dark"); + } else { + // it's not dark + setActualTheme("light"); + } + } + if (typeof window === "undefined") return; + const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + try { + // Chrome & Firefox + darkMediaQuery.addEventListener("change", ({ matches }) => { + if (matches) { + setActualTheme("dark"); + } else { + setActualTheme("light"); + } + }); + } catch (error) { + // Safari < 14 + darkMediaQuery.addListener(({ matches }) => { + try { + if (matches) { + setActualTheme("dark"); + } else { + setActualTheme("light"); + } + } catch (e) { + console.error(e); + } + }); + } + }, [theme]); + React$a.useEffect(() => { + // Ensure expanded is always false when no toasts are present / only one left + if (toasts.length <= 1) { + setExpanded(false); + } + }, [toasts]); + React$a.useEffect(() => { + const handleKeyDown = (event) => { + var _listRef_current; + const isHotkeyPressed = hotkey.every((key) => event[key] || event.code === key); + if (isHotkeyPressed) { + var _listRef_current1; + setExpanded(true); + (_listRef_current1 = listRef.current) == null ? void 0 : _listRef_current1.focus(); + } + if ( + event.code === "Escape" && + (document.activeElement === listRef.current || ((_listRef_current = listRef.current) == null ? void 0 : _listRef_current.contains(document.activeElement))) + ) { + setExpanded(false); + } + }; + document.addEventListener("keydown", handleKeyDown); + return () => document.removeEventListener("keydown", handleKeyDown); + }, [hotkey]); + React$a.useEffect(() => { + if (listRef.current) { + return () => { + if (lastFocusedElementRef.current) { + lastFocusedElementRef.current.focus({ + preventScroll: true, + }); + lastFocusedElementRef.current = null; + isFocusWithinRef.current = false; + } + }; + } + }, [listRef.current]); + return ( + // Remove item from normal navigation flow, only available via hotkey + /*#__PURE__*/ React$a.createElement( + "section", + { + "ref": ref, + "aria-label": `${containerAriaLabel} ${hotkeyLabel}`, + "tabIndex": -1, + "aria-live": "polite", + "aria-relevant": "additions text", + "aria-atomic": "false", + "suppressHydrationWarning": true, + }, + possiblePositions.map((position, index) => { + var _heights_; + const [y, x] = position.split("-"); + if (!filteredToasts.length) return null; + return /*#__PURE__*/ React$a.createElement( + "ol", + { + "key": position, + "dir": dir === "auto" ? getDocumentDirection() : dir, + "tabIndex": -1, + "ref": listRef, + "className": className, + "data-sonner-toaster": true, + "data-sonner-theme": actualTheme, + "data-y-position": y, + "data-x-position": x, + "style": { + "--front-toast-height": `${((_heights_ = heights[0]) == null ? void 0 : _heights_.height) || 0}px`, + "--width": `${TOAST_WIDTH}px`, + "--gap": `${gap}px`, + ...style, + ...assignOffset(offset, mobileOffset), + }, + "onBlur": (event) => { + if (isFocusWithinRef.current && !event.currentTarget.contains(event.relatedTarget)) { + isFocusWithinRef.current = false; + if (lastFocusedElementRef.current) { + lastFocusedElementRef.current.focus({ + preventScroll: true, + }); + lastFocusedElementRef.current = null; + } + } + }, + "onFocus": (event) => { + const isNotDismissible = event.target instanceof HTMLElement && event.target.dataset.dismissible === "false"; + if (isNotDismissible) return; + if (!isFocusWithinRef.current) { + isFocusWithinRef.current = true; + lastFocusedElementRef.current = event.relatedTarget; + } + }, + "onMouseEnter": () => setExpanded(true), + "onMouseMove": () => setExpanded(true), + "onMouseLeave": () => { + // Avoid setting expanded to false when interacting with a toast, e.g. swiping + if (!interacting) { + setExpanded(false); + } + }, + "onDragEnd": () => setExpanded(false), + "onPointerDown": (event) => { + const isNotDismissible = event.target instanceof HTMLElement && event.target.dataset.dismissible === "false"; + if (isNotDismissible) return; + setInteracting(true); + }, + "onPointerUp": () => setInteracting(false), + }, + filteredToasts + .filter((toast) => (!toast.position && index === 0) || toast.position === position) + .map((toast, index) => { + var _toastOptions_duration, _toastOptions_closeButton; + return /*#__PURE__*/ React$a.createElement(Toast, { + key: toast.id, + icons: icons, + index: index, + toast: toast, + defaultRichColors: richColors, + duration: (_toastOptions_duration = toastOptions == null ? void 0 : toastOptions.duration) != null ? _toastOptions_duration : duration, + className: toastOptions == null ? void 0 : toastOptions.className, + descriptionClassName: toastOptions == null ? void 0 : toastOptions.descriptionClassName, + invert: invert, + visibleToasts: visibleToasts, + closeButton: (_toastOptions_closeButton = toastOptions == null ? void 0 : toastOptions.closeButton) != null ? _toastOptions_closeButton : closeButton, + interacting: interacting, + position: position, + style: toastOptions == null ? void 0 : toastOptions.style, + unstyled: toastOptions == null ? void 0 : toastOptions.unstyled, + classNames: toastOptions == null ? void 0 : toastOptions.classNames, + cancelButtonStyle: toastOptions == null ? void 0 : toastOptions.cancelButtonStyle, + actionButtonStyle: toastOptions == null ? void 0 : toastOptions.actionButtonStyle, + closeButtonAriaLabel: toastOptions == null ? void 0 : toastOptions.closeButtonAriaLabel, + removeToast: removeToast, + toasts: filteredToasts.filter((t) => t.position == toast.position), + heights: heights.filter((h) => h.position == toast.position), + setHeights: setHeights, + expandByDefault: expand, + gap: gap, + expanded: expanded, + swipeDirections: props.swipeDirections, + }); + }), + ); + }), + ) + ); +}); + +const isString$1 = (obj) => typeof obj === "string"; +const defer = () => { + let res; + let rej; + const promise = new Promise((resolve, reject) => { + res = resolve; + rej = reject; + }); + promise.resolve = res; + promise.reject = rej; + return promise; +}; +const makeString = (object) => { + if (object == null) return ""; + return String(object); +}; +const copy = (a, s, t) => { + a.forEach((m) => { + if (s[m]) t[m] = s[m]; + }); +}; +const lastOfPathSeparatorRegExp = /###/g; +const cleanKey = (key) => (key && key.includes("###") ? key.replace(lastOfPathSeparatorRegExp, ".") : key); +const canNotTraverseDeeper = (object) => !object || isString$1(object); +const getLastOfPath = (object, path, Empty) => { + const stack = !isString$1(path) ? path : path.split("."); + let stackIndex = 0; + while (stackIndex < stack.length - 1) { + if (canNotTraverseDeeper(object)) return {}; + const key = cleanKey(stack[stackIndex]); + if (!object[key] && Empty) object[key] = new Empty(); + if (Object.prototype.hasOwnProperty.call(object, key)) { + object = object[key]; + } else { + object = {}; + } + ++stackIndex; + } + if (canNotTraverseDeeper(object)) return {}; + return { + obj: object, + k: cleanKey(stack[stackIndex]), + }; +}; +const setPath = (object, path, newValue) => { + const { obj, k } = getLastOfPath(object, path, Object); + if (obj !== undefined || path.length === 1) { + obj[k] = newValue; + return; + } + let e = path[path.length - 1]; + let p = path.slice(0, path.length - 1); + let last = getLastOfPath(object, p, Object); + while (last.obj === undefined && p.length) { + e = `${p[p.length - 1]}.${e}`; + p = p.slice(0, p.length - 1); + last = getLastOfPath(object, p, Object); + if (last?.obj && typeof last.obj[`${last.k}.${e}`] !== "undefined") { + last.obj = undefined; + } + } + last.obj[`${last.k}.${e}`] = newValue; +}; +const pushPath = (object, path, newValue, concat) => { + const { obj, k } = getLastOfPath(object, path, Object); + obj[k] = obj[k] || []; + obj[k].push(newValue); +}; +const getPath = (object, path) => { + const { obj, k } = getLastOfPath(object, path); + if (!obj) return undefined; + if (!Object.prototype.hasOwnProperty.call(obj, k)) return undefined; + return obj[k]; +}; +const getPathWithDefaults = (data, defaultData, key) => { + const value = getPath(data, key); + if (value !== undefined) { + return value; + } + return getPath(defaultData, key); +}; +const deepExtend = (target, source, overwrite) => { + for (const prop in source) { + if (prop !== "__proto__" && prop !== "constructor") { + if (prop in target) { + if (isString$1(target[prop]) || target[prop] instanceof String || isString$1(source[prop]) || source[prop] instanceof String) { + if (overwrite) target[prop] = source[prop]; + } else { + deepExtend(target[prop], source[prop], overwrite); + } + } else { + target[prop] = source[prop]; + } + } + } + return target; +}; +const regexEscape = (str) => str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); +const _entityMap = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "/": "/", +}; +const escape = (data) => { + if (isString$1(data)) { + return data.replace(/[&<>"'\/]/g, (s) => _entityMap[s]); + } + return data; +}; +class RegExpCache { + constructor(capacity) { + this.capacity = capacity; + this.regExpMap = new Map(); + this.regExpQueue = []; + } + getRegExp(pattern) { + const regExpFromCache = this.regExpMap.get(pattern); + if (regExpFromCache !== undefined) { + return regExpFromCache; + } + const regExpNew = new RegExp(pattern); + if (this.regExpQueue.length === this.capacity) { + this.regExpMap.delete(this.regExpQueue.shift()); + } + this.regExpMap.set(pattern, regExpNew); + this.regExpQueue.push(pattern); + return regExpNew; + } +} +const chars = [" ", ",", "?", "!", ";"]; +const looksLikeObjectPathRegExpCache = new RegExpCache(20); +const looksLikeObjectPath = (key, nsSeparator, keySeparator) => { + nsSeparator = nsSeparator || ""; + keySeparator = keySeparator || ""; + const possibleChars = chars.filter((c) => !nsSeparator.includes(c) && !keySeparator.includes(c)); + if (possibleChars.length === 0) return true; + const r = looksLikeObjectPathRegExpCache.getRegExp(`(${possibleChars.map((c) => (c === "?" ? "\\?" : c)).join("|")})`); + let matched = !r.test(key); + if (!matched) { + const ki = key.indexOf(keySeparator); + if (ki > 0 && !r.test(key.substring(0, ki))) { + matched = true; + } + } + return matched; +}; +const deepFind = (obj, path, keySeparator = ".") => { + if (!obj) return undefined; + if (obj[path]) { + if (!Object.prototype.hasOwnProperty.call(obj, path)) return undefined; + return obj[path]; + } + const tokens = path.split(keySeparator); + let current = obj; + for (let i = 0; i < tokens.length; ) { + if (!current || typeof current !== "object") { + return undefined; + } + let next; + let nextPath = ""; + for (let j = i; j < tokens.length; ++j) { + if (j !== i) { + nextPath += keySeparator; + } + nextPath += tokens[j]; + next = current[nextPath]; + if (next !== undefined) { + if (["string", "number", "boolean"].includes(typeof next) && j < tokens.length - 1) { + continue; + } + i += j - i + 1; + break; + } + } + current = next; + } + return current; +}; +const getCleanedCode = (code) => code?.replace(/_/g, "-"); + +const consoleLogger = { + type: "logger", + log(args) { + this.output("log", args); + }, + warn(args) { + this.output("warn", args); + }, + error(args) { + this.output("error", args); + }, + output(type, args) { + console?.[type]?.apply?.(console, args); + }, +}; +class Logger { + constructor(concreteLogger, options = {}) { + this.init(concreteLogger, options); + } + init(concreteLogger, options = {}) { + this.prefix = options.prefix || "i18next:"; + this.logger = concreteLogger || consoleLogger; + this.options = options; + this.debug = options.debug; + } + log(...args) { + return this.forward(args, "log", "", true); + } + warn(...args) { + return this.forward(args, "warn", "", true); + } + error(...args) { + return this.forward(args, "error", ""); + } + deprecate(...args) { + return this.forward(args, "warn", "WARNING DEPRECATED: ", true); + } + forward(args, lvl, prefix, debugOnly) { + if (debugOnly && !this.debug) return null; + if (isString$1(args[0])) args[0] = `${prefix}${this.prefix} ${args[0]}`; + return this.logger[lvl](args); + } + create(moduleName) { + return new Logger(this.logger, { + ...{ + prefix: `${this.prefix}:${moduleName}:`, + }, + ...this.options, + }); + } + clone(options) { + options = options || this.options; + options.prefix = options.prefix || this.prefix; + return new Logger(this.logger, options); + } +} +var baseLogger = new Logger(); + +class EventEmitter { + constructor() { + this.observers = {}; + } + on(events, listener) { + events.split(" ").forEach((event) => { + if (!this.observers[event]) this.observers[event] = new Map(); + const numListeners = this.observers[event].get(listener) || 0; + this.observers[event].set(listener, numListeners + 1); + }); + return this; + } + off(event, listener) { + if (!this.observers[event]) return; + if (!listener) { + delete this.observers[event]; + return; + } + this.observers[event].delete(listener); + } + once(event, listener) { + const wrapper = (...args) => { + listener(...args); + this.off(event, wrapper); + }; + this.on(event, wrapper); + return this; + } + emit(event, ...args) { + if (this.observers[event]) { + const cloned = Array.from(this.observers[event].entries()); + cloned.forEach(([observer, numTimesAdded]) => { + for (let i = 0; i < numTimesAdded; i++) { + observer(...args); + } + }); + } + if (this.observers["*"]) { + const cloned = Array.from(this.observers["*"].entries()); + cloned.forEach(([observer, numTimesAdded]) => { + for (let i = 0; i < numTimesAdded; i++) { + observer(event, ...args); + } + }); + } + } +} + +class ResourceStore extends EventEmitter { + constructor( + data, + options = { + ns: ["translation"], + defaultNS: "translation", + }, + ) { + super(); + this.data = data || {}; + this.options = options; + if (this.options.keySeparator === undefined) { + this.options.keySeparator = "."; + } + if (this.options.ignoreJSONStructure === undefined) { + this.options.ignoreJSONStructure = true; + } + } + addNamespaces(ns) { + if (!this.options.ns.includes(ns)) { + this.options.ns.push(ns); + } + } + removeNamespaces(ns) { + const index = this.options.ns.indexOf(ns); + if (index > -1) { + this.options.ns.splice(index, 1); + } + } + getResource(lng, ns, key, options = {}) { + const keySeparator = options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator; + const ignoreJSONStructure = options.ignoreJSONStructure !== undefined ? options.ignoreJSONStructure : this.options.ignoreJSONStructure; + let path; + if (lng.includes(".")) { + path = lng.split("."); + } else { + path = [lng, ns]; + if (key) { + if (Array.isArray(key)) { + path.push(...key); + } else if (isString$1(key) && keySeparator) { + path.push(...key.split(keySeparator)); + } else { + path.push(key); + } + } + } + const result = getPath(this.data, path); + if (!result && !ns && !key && lng.includes(".")) { + lng = path[0]; + ns = path[1]; + key = path.slice(2).join("."); + } + if (result || !ignoreJSONStructure || !isString$1(key)) return result; + return deepFind(this.data?.[lng]?.[ns], key, keySeparator); + } + addResource( + lng, + ns, + key, + value, + options = { + silent: false, + }, + ) { + const keySeparator = options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator; + let path = [lng, ns]; + if (key) path = path.concat(keySeparator ? key.split(keySeparator) : key); + if (lng.includes(".")) { + path = lng.split("."); + value = ns; + ns = path[1]; + } + this.addNamespaces(ns); + setPath(this.data, path, value); + if (!options.silent) this.emit("added", lng, ns, key, value); + } + addResources( + lng, + ns, + resources, + options = { + silent: false, + }, + ) { + for (const m in resources) { + if (isString$1(resources[m]) || Array.isArray(resources[m])) + this.addResource(lng, ns, m, resources[m], { + silent: true, + }); + } + if (!options.silent) this.emit("added", lng, ns, resources); + } + addResourceBundle( + lng, + ns, + resources, + deep, + overwrite, + options = { + silent: false, + skipCopy: false, + }, + ) { + let path = [lng, ns]; + if (lng.includes(".")) { + path = lng.split("."); + deep = resources; + resources = ns; + ns = path[1]; + } + this.addNamespaces(ns); + let pack = getPath(this.data, path) || {}; + if (!options.skipCopy) resources = JSON.parse(JSON.stringify(resources)); + if (deep) { + deepExtend(pack, resources, overwrite); + } else { + pack = { + ...pack, + ...resources, + }; + } + setPath(this.data, path, pack); + if (!options.silent) this.emit("added", lng, ns, resources); + } + removeResourceBundle(lng, ns) { + if (this.hasResourceBundle(lng, ns)) { + delete this.data[lng][ns]; + } + this.removeNamespaces(ns); + this.emit("removed", lng, ns); + } + hasResourceBundle(lng, ns) { + return this.getResource(lng, ns) !== undefined; + } + getResourceBundle(lng, ns) { + if (!ns) ns = this.options.defaultNS; + return this.getResource(lng, ns); + } + getDataByLanguage(lng) { + return this.data[lng]; + } + hasLanguageSomeTranslations(lng) { + const data = this.getDataByLanguage(lng); + const n = (data && Object.keys(data)) || []; + return !!n.find((v) => data[v] && Object.keys(data[v]).length > 0); + } + toJSON() { + return this.data; + } +} + +var postProcessor = { + processors: {}, + addPostProcessor(module) { + this.processors[module.name] = module; + }, + handle(processors, value, key, options, translator) { + processors.forEach((processor) => { + value = this.processors[processor]?.process(value, key, options, translator) ?? value; + }); + return value; + }, +}; + +const PATH_KEY = Symbol("i18next/PATH_KEY"); +function createProxy() { + const state = []; + const handler = Object.create(null); + let proxy; + handler.get = (target, key) => { + proxy?.revoke?.(); + if (key === PATH_KEY) return state; + state.push(key); + proxy = Proxy.revocable(target, handler); + return proxy.proxy; + }; + return Proxy.revocable(Object.create(null), handler).proxy; +} +function keysFromSelector(selector, opts) { + const { [PATH_KEY]: path } = selector(createProxy()); + const keySeparator = opts?.keySeparator ?? "."; + const nsSeparator = opts?.nsSeparator ?? ":"; + if (path.length > 1 && nsSeparator) { + const ns = opts?.ns; + const nsArray = Array.isArray(ns) ? ns : null; + if (nsArray && nsArray.length > 1 && nsArray.slice(1).includes(path[0])) { + return `${path[0]}${nsSeparator}${path.slice(1).join(keySeparator)}`; + } + } + return path.join(keySeparator); +} + +const shouldHandleAsObject = (res) => !isString$1(res) && typeof res !== "boolean" && typeof res !== "number"; +class Translator extends EventEmitter { + constructor(services, options = {}) { + super(); + copy(["resourceStore", "languageUtils", "pluralResolver", "interpolator", "backendConnector", "i18nFormat", "utils"], services, this); + this.options = options; + if (this.options.keySeparator === undefined) { + this.options.keySeparator = "."; + } + this.logger = baseLogger.create("translator"); + this.checkedLoadedFor = {}; + } + changeLanguage(lng) { + if (lng) this.language = lng; + } + exists( + key, + o = { + interpolation: {}, + }, + ) { + const opt = { + ...o, + }; + if (key == null) return false; + const resolved = this.resolve(key, opt); + if (resolved?.res === undefined) return false; + const isObject = shouldHandleAsObject(resolved.res); + if (opt.returnObjects === false && isObject) { + return false; + } + return true; + } + extractFromKey(key, opt) { + let nsSeparator = opt.nsSeparator !== undefined ? opt.nsSeparator : this.options.nsSeparator; + if (nsSeparator === undefined) nsSeparator = ":"; + const keySeparator = opt.keySeparator !== undefined ? opt.keySeparator : this.options.keySeparator; + let namespaces = opt.ns || this.options.defaultNS || []; + const wouldCheckForNsInKey = nsSeparator && key.includes(nsSeparator); + const seemsNaturalLanguage = + !this.options.userDefinedKeySeparator && !opt.keySeparator && !this.options.userDefinedNsSeparator && !opt.nsSeparator && !looksLikeObjectPath(key, nsSeparator, keySeparator); + if (wouldCheckForNsInKey && !seemsNaturalLanguage) { + const m = key.match(this.interpolator.nestingRegexp); + if (m && m.length > 0) { + return { + key, + namespaces: isString$1(namespaces) ? [namespaces] : namespaces, + }; + } + const parts = key.split(nsSeparator); + if (nsSeparator !== keySeparator || (nsSeparator === keySeparator && this.options.ns.includes(parts[0]))) namespaces = parts.shift(); + key = parts.join(keySeparator); + } + return { + key, + namespaces: isString$1(namespaces) ? [namespaces] : namespaces, + }; + } + translate(keys, o, lastKey) { + let opt = + typeof o === "object" ? + { + ...o, + } + : o; + if (typeof opt !== "object" && this.options.overloadTranslationOptionHandler) { + opt = this.options.overloadTranslationOptionHandler(arguments); + } + if (typeof opt === "object") + opt = { + ...opt, + }; + if (!opt) opt = {}; + if (keys == null) return ""; + if (typeof keys === "function") + keys = keysFromSelector(keys, { + ...this.options, + ...opt, + }); + if (!Array.isArray(keys)) keys = [String(keys)]; + keys = keys.map((k) => + typeof k === "function" ? + keysFromSelector(k, { + ...this.options, + ...opt, + }) + : String(k), + ); + const returnDetails = opt.returnDetails !== undefined ? opt.returnDetails : this.options.returnDetails; + const keySeparator = opt.keySeparator !== undefined ? opt.keySeparator : this.options.keySeparator; + const { key, namespaces } = this.extractFromKey(keys[keys.length - 1], opt); + const namespace = namespaces[namespaces.length - 1]; + let nsSeparator = opt.nsSeparator !== undefined ? opt.nsSeparator : this.options.nsSeparator; + if (nsSeparator === undefined) nsSeparator = ":"; + const lng = opt.lng || this.language; + const appendNamespaceToCIMode = opt.appendNamespaceToCIMode || this.options.appendNamespaceToCIMode; + if (lng?.toLowerCase() === "cimode") { + if (appendNamespaceToCIMode) { + if (returnDetails) { + return { + res: `${namespace}${nsSeparator}${key}`, + usedKey: key, + exactUsedKey: key, + usedLng: lng, + usedNS: namespace, + usedParams: this.getUsedParamsDetails(opt), + }; + } + return `${namespace}${nsSeparator}${key}`; + } + if (returnDetails) { + return { + res: key, + usedKey: key, + exactUsedKey: key, + usedLng: lng, + usedNS: namespace, + usedParams: this.getUsedParamsDetails(opt), + }; + } + return key; + } + const resolved = this.resolve(keys, opt); + let res = resolved?.res; + const resUsedKey = resolved?.usedKey || key; + const resExactUsedKey = resolved?.exactUsedKey || key; + const noObject = ["[object Number]", "[object Function]", "[object RegExp]"]; + const joinArrays = opt.joinArrays !== undefined ? opt.joinArrays : this.options.joinArrays; + const handleAsObjectInI18nFormat = !this.i18nFormat || this.i18nFormat.handleAsObject; + const needsPluralHandling = opt.count !== undefined && !isString$1(opt.count); + const hasDefaultValue = Translator.hasDefaultValue(opt); + const defaultValueSuffix = needsPluralHandling ? this.pluralResolver.getSuffix(lng, opt.count, opt) : ""; + const defaultValueSuffixOrdinalFallback = + opt.ordinal && needsPluralHandling ? + this.pluralResolver.getSuffix(lng, opt.count, { + ordinal: false, + }) + : ""; + const needsZeroSuffixLookup = needsPluralHandling && !opt.ordinal && opt.count === 0; + const defaultValue = + (needsZeroSuffixLookup && opt[`defaultValue${this.options.pluralSeparator}zero`]) || + opt[`defaultValue${defaultValueSuffix}`] || + opt[`defaultValue${defaultValueSuffixOrdinalFallback}`] || + opt.defaultValue; + let resForObjHndl = res; + if (handleAsObjectInI18nFormat && !res && hasDefaultValue) { + resForObjHndl = defaultValue; + } + const handleAsObject = shouldHandleAsObject(resForObjHndl); + const resType = Object.prototype.toString.apply(resForObjHndl); + if (handleAsObjectInI18nFormat && resForObjHndl && handleAsObject && !noObject.includes(resType) && !(isString$1(joinArrays) && Array.isArray(resForObjHndl))) { + if (!opt.returnObjects && !this.options.returnObjects) { + if (!this.options.returnedObjectHandler) { + this.logger.warn("accessing an object - but returnObjects options is not enabled!"); + } + const r = + this.options.returnedObjectHandler ? + this.options.returnedObjectHandler(resUsedKey, resForObjHndl, { + ...opt, + ns: namespaces, + }) + : `key '${key} (${this.language})' returned an object instead of string.`; + if (returnDetails) { + resolved.res = r; + resolved.usedParams = this.getUsedParamsDetails(opt); + return resolved; + } + return r; + } + if (keySeparator) { + const resTypeIsArray = Array.isArray(resForObjHndl); + const copy = resTypeIsArray ? [] : {}; + const newKeyToUse = resTypeIsArray ? resExactUsedKey : resUsedKey; + for (const m in resForObjHndl) { + if (Object.prototype.hasOwnProperty.call(resForObjHndl, m)) { + const deepKey = `${newKeyToUse}${keySeparator}${m}`; + if (hasDefaultValue && !res) { + copy[m] = this.translate(deepKey, { + ...opt, + defaultValue: shouldHandleAsObject(defaultValue) ? defaultValue[m] : undefined, + ...{ + joinArrays: false, + ns: namespaces, + }, + }); + } else { + copy[m] = this.translate(deepKey, { + ...opt, + ...{ + joinArrays: false, + ns: namespaces, + }, + }); + } + if (copy[m] === deepKey) copy[m] = resForObjHndl[m]; + } + } + res = copy; + } + } else if (handleAsObjectInI18nFormat && isString$1(joinArrays) && Array.isArray(res)) { + res = res.join(joinArrays); + if (res) res = this.extendTranslation(res, keys, opt, lastKey); + } else { + let usedDefault = false; + let usedKey = false; + if (!this.isValidLookup(res) && hasDefaultValue) { + usedDefault = true; + res = defaultValue; + } + if (!this.isValidLookup(res)) { + usedKey = true; + res = key; + } + const missingKeyNoValueFallbackToKey = opt.missingKeyNoValueFallbackToKey || this.options.missingKeyNoValueFallbackToKey; + const resForMissing = missingKeyNoValueFallbackToKey && usedKey ? undefined : res; + const updateMissing = hasDefaultValue && defaultValue !== res && this.options.updateMissing; + if (usedKey || usedDefault || updateMissing) { + this.logger.log(updateMissing ? "updateKey" : "missingKey", lng, namespace, key, updateMissing ? defaultValue : res); + if (keySeparator) { + const fk = this.resolve(key, { + ...opt, + keySeparator: false, + }); + if (fk && fk.res) + this.logger.warn( + "Seems the loaded translations were in flat JSON format instead of nested. Either set keySeparator: false on init or make sure your translations are published in nested format.", + ); + } + let lngs = []; + const fallbackLngs = this.languageUtils.getFallbackCodes(this.options.fallbackLng, opt.lng || this.language); + if (this.options.saveMissingTo === "fallback" && fallbackLngs && fallbackLngs[0]) { + for (let i = 0; i < fallbackLngs.length; i++) { + lngs.push(fallbackLngs[i]); + } + } else if (this.options.saveMissingTo === "all") { + lngs = this.languageUtils.toResolveHierarchy(opt.lng || this.language); + } else { + lngs.push(opt.lng || this.language); + } + const send = (l, k, specificDefaultValue) => { + const defaultForMissing = hasDefaultValue && specificDefaultValue !== res ? specificDefaultValue : resForMissing; + if (this.options.missingKeyHandler) { + this.options.missingKeyHandler(l, namespace, k, defaultForMissing, updateMissing, opt); + } else if (this.backendConnector?.saveMissing) { + this.backendConnector.saveMissing(l, namespace, k, defaultForMissing, updateMissing, opt); + } + this.emit("missingKey", l, namespace, k, res); + }; + if (this.options.saveMissing) { + if (this.options.saveMissingPlurals && needsPluralHandling) { + lngs.forEach((language) => { + const suffixes = this.pluralResolver.getSuffixes(language, opt); + if (needsZeroSuffixLookup && opt[`defaultValue${this.options.pluralSeparator}zero`] && !suffixes.includes(`${this.options.pluralSeparator}zero`)) { + suffixes.push(`${this.options.pluralSeparator}zero`); + } + suffixes.forEach((suffix) => { + send([language], key + suffix, opt[`defaultValue${suffix}`] || defaultValue); + }); + }); + } else { + send(lngs, key, defaultValue); + } + } + } + res = this.extendTranslation(res, keys, opt, resolved, lastKey); + if (usedKey && res === key && this.options.appendNamespaceToMissingKey) { + res = `${namespace}${nsSeparator}${key}`; + } + if ((usedKey || usedDefault) && this.options.parseMissingKeyHandler) { + res = this.options.parseMissingKeyHandler(this.options.appendNamespaceToMissingKey ? `${namespace}${nsSeparator}${key}` : key, usedDefault ? res : undefined, opt); + } + } + if (returnDetails) { + resolved.res = res; + resolved.usedParams = this.getUsedParamsDetails(opt); + return resolved; + } + return res; + } + extendTranslation(res, key, opt, resolved, lastKey) { + if (this.i18nFormat?.parse) { + res = this.i18nFormat.parse( + res, + { + ...this.options.interpolation.defaultVariables, + ...opt, + }, + opt.lng || this.language || resolved.usedLng, + resolved.usedNS, + resolved.usedKey, + { + resolved, + }, + ); + } else if (!opt.skipInterpolation) { + if (opt.interpolation) + this.interpolator.init({ + ...opt, + ...{ + interpolation: { + ...this.options.interpolation, + ...opt.interpolation, + }, + }, + }); + const skipOnVariables = isString$1(res) && (opt?.interpolation?.skipOnVariables !== undefined ? opt.interpolation.skipOnVariables : this.options.interpolation.skipOnVariables); + let nestBef; + if (skipOnVariables) { + const nb = res.match(this.interpolator.nestingRegexp); + nestBef = nb && nb.length; + } + let data = opt.replace && !isString$1(opt.replace) ? opt.replace : opt; + if (this.options.interpolation.defaultVariables) + data = { + ...this.options.interpolation.defaultVariables, + ...data, + }; + res = this.interpolator.interpolate(res, data, opt.lng || this.language || resolved.usedLng, opt); + if (skipOnVariables) { + const na = res.match(this.interpolator.nestingRegexp); + const nestAft = na && na.length; + if (nestBef < nestAft) opt.nest = false; + } + if (!opt.lng && resolved && resolved.res) opt.lng = this.language || resolved.usedLng; + if (opt.nest !== false) + res = this.interpolator.nest( + res, + (...args) => { + if (lastKey?.[0] === args[0] && !opt.context) { + this.logger.warn(`It seems you are nesting recursively key: ${args[0]} in key: ${key[0]}`); + return null; + } + return this.translate(...args, key); + }, + opt, + ); + if (opt.interpolation) this.interpolator.reset(); + } + const postProcess = opt.postProcess || this.options.postProcess; + const postProcessorNames = isString$1(postProcess) ? [postProcess] : postProcess; + if (res != null && postProcessorNames?.length && opt.applyPostProcessor !== false) { + res = postProcessor.handle( + postProcessorNames, + res, + key, + this.options && this.options.postProcessPassResolved ? + { + i18nResolved: { + ...resolved, + usedParams: this.getUsedParamsDetails(opt), + }, + ...opt, + } + : opt, + this, + ); + } + return res; + } + resolve(keys, opt = {}) { + let found; + let usedKey; + let exactUsedKey; + let usedLng; + let usedNS; + if (isString$1(keys)) keys = [keys]; + if (Array.isArray(keys)) + keys = keys.map((k) => + typeof k === "function" ? + keysFromSelector(k, { + ...this.options, + ...opt, + }) + : k, + ); + keys.forEach((k) => { + if (this.isValidLookup(found)) return; + const extracted = this.extractFromKey(k, opt); + const key = extracted.key; + usedKey = key; + let namespaces = extracted.namespaces; + if (this.options.fallbackNS) namespaces = namespaces.concat(this.options.fallbackNS); + const needsPluralHandling = opt.count !== undefined && !isString$1(opt.count); + const needsZeroSuffixLookup = needsPluralHandling && !opt.ordinal && opt.count === 0; + const needsContextHandling = opt.context !== undefined && (isString$1(opt.context) || typeof opt.context === "number") && opt.context !== ""; + const codes = opt.lngs ? opt.lngs : this.languageUtils.toResolveHierarchy(opt.lng || this.language, opt.fallbackLng); + namespaces.forEach((ns) => { + if (this.isValidLookup(found)) return; + usedNS = ns; + if (!this.checkedLoadedFor[`${codes[0]}-${ns}`] && this.utils?.hasLoadedNamespace && !this.utils?.hasLoadedNamespace(usedNS)) { + this.checkedLoadedFor[`${codes[0]}-${ns}`] = true; + this.logger.warn( + `key "${usedKey}" for languages "${codes.join(", ")}" won't get resolved as namespace "${usedNS}" was not yet loaded`, + "This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!", + ); + } + codes.forEach((code) => { + if (this.isValidLookup(found)) return; + usedLng = code; + const finalKeys = [key]; + if (this.i18nFormat?.addLookupKeys) { + this.i18nFormat.addLookupKeys(finalKeys, key, code, ns, opt); + } else { + let pluralSuffix; + if (needsPluralHandling) pluralSuffix = this.pluralResolver.getSuffix(code, opt.count, opt); + const zeroSuffix = `${this.options.pluralSeparator}zero`; + const ordinalPrefix = `${this.options.pluralSeparator}ordinal${this.options.pluralSeparator}`; + if (needsPluralHandling) { + if (opt.ordinal && pluralSuffix.startsWith(ordinalPrefix)) { + finalKeys.push(key + pluralSuffix.replace(ordinalPrefix, this.options.pluralSeparator)); + } + finalKeys.push(key + pluralSuffix); + if (needsZeroSuffixLookup) { + finalKeys.push(key + zeroSuffix); + } + } + if (needsContextHandling) { + const contextKey = `${key}${this.options.contextSeparator || "_"}${opt.context}`; + finalKeys.push(contextKey); + if (needsPluralHandling) { + if (opt.ordinal && pluralSuffix.startsWith(ordinalPrefix)) { + finalKeys.push(contextKey + pluralSuffix.replace(ordinalPrefix, this.options.pluralSeparator)); + } + finalKeys.push(contextKey + pluralSuffix); + if (needsZeroSuffixLookup) { + finalKeys.push(contextKey + zeroSuffix); + } + } + } + } + let possibleKey; + while ((possibleKey = finalKeys.pop())) { + if (!this.isValidLookup(found)) { + exactUsedKey = possibleKey; + found = this.getResource(code, ns, possibleKey, opt); + } + } + }); + }); + }); + return { + res: found, + usedKey, + exactUsedKey, + usedLng, + usedNS, + }; + } + isValidLookup(res) { + return res !== undefined && !(!this.options.returnNull && res === null) && !(!this.options.returnEmptyString && res === ""); + } + getResource(code, ns, key, options = {}) { + if (this.i18nFormat?.getResource) return this.i18nFormat.getResource(code, ns, key, options); + return this.resourceStore.getResource(code, ns, key, options); + } + getUsedParamsDetails(options = {}) { + const optionsKeys = [ + "defaultValue", + "ordinal", + "context", + "replace", + "lng", + "lngs", + "fallbackLng", + "ns", + "keySeparator", + "nsSeparator", + "returnObjects", + "returnDetails", + "joinArrays", + "postProcess", + "interpolation", + ]; + const useOptionsReplaceForData = options.replace && !isString$1(options.replace); + let data = useOptionsReplaceForData ? options.replace : options; + if (useOptionsReplaceForData && typeof options.count !== "undefined") { + data.count = options.count; + } + if (this.options.interpolation.defaultVariables) { + data = { + ...this.options.interpolation.defaultVariables, + ...data, + }; + } + if (!useOptionsReplaceForData) { + data = { + ...data, + }; + for (const key of optionsKeys) { + delete data[key]; + } + } + return data; + } + static hasDefaultValue(options) { + const prefix = "defaultValue"; + for (const option in options) { + if (Object.prototype.hasOwnProperty.call(options, option) && option.startsWith(prefix) && undefined !== options[option]) { + return true; + } + } + return false; + } +} + +class LanguageUtil { + constructor(options) { + this.options = options; + this.supportedLngs = this.options.supportedLngs || false; + this.logger = baseLogger.create("languageUtils"); + } + getScriptPartFromCode(code) { + code = getCleanedCode(code); + if (!code || !code.includes("-")) return null; + const p = code.split("-"); + if (p.length === 2) return null; + p.pop(); + if (p[p.length - 1].toLowerCase() === "x") return null; + return this.formatLanguageCode(p.join("-")); + } + getLanguagePartFromCode(code) { + code = getCleanedCode(code); + if (!code || !code.includes("-")) return code; + const p = code.split("-"); + return this.formatLanguageCode(p[0]); + } + formatLanguageCode(code) { + if (isString$1(code) && code.includes("-")) { + let formattedCode; + try { + formattedCode = Intl.getCanonicalLocales(code)[0]; + } catch (e) {} + if (formattedCode && this.options.lowerCaseLng) { + formattedCode = formattedCode.toLowerCase(); + } + if (formattedCode) return formattedCode; + if (this.options.lowerCaseLng) { + return code.toLowerCase(); + } + return code; + } + return this.options.cleanCode || this.options.lowerCaseLng ? code.toLowerCase() : code; + } + isSupportedCode(code) { + if (this.options.load === "languageOnly" || this.options.nonExplicitSupportedLngs) { + code = this.getLanguagePartFromCode(code); + } + return !this.supportedLngs || !this.supportedLngs.length || this.supportedLngs.includes(code); + } + getBestMatchFromCodes(codes) { + if (!codes) return null; + let found; + codes.forEach((code) => { + if (found) return; + const cleanedLng = this.formatLanguageCode(code); + if (!this.options.supportedLngs || this.isSupportedCode(cleanedLng)) found = cleanedLng; + }); + if (!found && this.options.supportedLngs) { + codes.forEach((code) => { + if (found) return; + const lngScOnly = this.getScriptPartFromCode(code); + if (this.isSupportedCode(lngScOnly)) return (found = lngScOnly); + const lngOnly = this.getLanguagePartFromCode(code); + if (this.isSupportedCode(lngOnly)) return (found = lngOnly); + found = this.options.supportedLngs.find((supportedLng) => { + if (supportedLng === lngOnly) return true; + if (!supportedLng.includes("-") && !lngOnly.includes("-")) return false; + if (supportedLng.includes("-") && !lngOnly.includes("-") && supportedLng.slice(0, supportedLng.indexOf("-")) === lngOnly) return true; + if (supportedLng.startsWith(lngOnly) && lngOnly.length > 1) return true; + return false; + }); + }); + } + if (!found) found = this.getFallbackCodes(this.options.fallbackLng)[0]; + return found; + } + getFallbackCodes(fallbacks, code) { + if (!fallbacks) return []; + if (typeof fallbacks === "function") fallbacks = fallbacks(code); + if (isString$1(fallbacks)) fallbacks = [fallbacks]; + if (Array.isArray(fallbacks)) return fallbacks; + if (!code) return fallbacks.default || []; + let found = fallbacks[code]; + if (!found) found = fallbacks[this.getScriptPartFromCode(code)]; + if (!found) found = fallbacks[this.formatLanguageCode(code)]; + if (!found) found = fallbacks[this.getLanguagePartFromCode(code)]; + if (!found) found = fallbacks.default; + return found || []; + } + toResolveHierarchy(code, fallbackCode) { + const fallbackCodes = this.getFallbackCodes((fallbackCode === false ? [] : fallbackCode) || this.options.fallbackLng || [], code); + const codes = []; + const addCode = (c) => { + if (!c) return; + if (this.isSupportedCode(c)) { + codes.push(c); + } else { + this.logger.warn(`rejecting language code not found in supportedLngs: ${c}`); + } + }; + if (isString$1(code) && (code.includes("-") || code.includes("_"))) { + if (this.options.load !== "languageOnly") addCode(this.formatLanguageCode(code)); + if (this.options.load !== "languageOnly" && this.options.load !== "currentOnly") addCode(this.getScriptPartFromCode(code)); + if (this.options.load !== "currentOnly") addCode(this.getLanguagePartFromCode(code)); + } else if (isString$1(code)) { + addCode(this.formatLanguageCode(code)); + } + fallbackCodes.forEach((fc) => { + if (!codes.includes(fc)) addCode(this.formatLanguageCode(fc)); + }); + return codes; + } +} + +const suffixesOrder = { + zero: 0, + one: 1, + two: 2, + few: 3, + many: 4, + other: 5, +}; +const dummyRule = { + select: (count) => (count === 1 ? "one" : "other"), + resolvedOptions: () => ({ + pluralCategories: ["one", "other"], + }), +}; +class PluralResolver { + constructor(languageUtils, options = {}) { + this.languageUtils = languageUtils; + this.options = options; + this.logger = baseLogger.create("pluralResolver"); + this.pluralRulesCache = {}; + } + clearCache() { + this.pluralRulesCache = {}; + } + getRule(code, options = {}) { + const cleanedCode = getCleanedCode(code === "dev" ? "en" : code); + const type = options.ordinal ? "ordinal" : "cardinal"; + const cacheKey = JSON.stringify({ + cleanedCode, + type, + }); + if (cacheKey in this.pluralRulesCache) { + return this.pluralRulesCache[cacheKey]; + } + let rule; + try { + rule = new Intl.PluralRules(cleanedCode, { + type, + }); + } catch (err) { + if (typeof Intl === "undefined") { + this.logger.error("No Intl support, please use an Intl polyfill!"); + return dummyRule; + } + if (!code.match(/-|_/)) return dummyRule; + const lngPart = this.languageUtils.getLanguagePartFromCode(code); + rule = this.getRule(lngPart, options); + } + this.pluralRulesCache[cacheKey] = rule; + return rule; + } + needsPlural(code, options = {}) { + let rule = this.getRule(code, options); + if (!rule) rule = this.getRule("dev", options); + return rule?.resolvedOptions().pluralCategories.length > 1; + } + getPluralFormsOfKey(code, key, options = {}) { + return this.getSuffixes(code, options).map((suffix) => `${key}${suffix}`); + } + getSuffixes(code, options = {}) { + let rule = this.getRule(code, options); + if (!rule) rule = this.getRule("dev", options); + if (!rule) return []; + return rule + .resolvedOptions() + .pluralCategories.sort((pluralCategory1, pluralCategory2) => suffixesOrder[pluralCategory1] - suffixesOrder[pluralCategory2]) + .map((pluralCategory) => `${this.options.prepend}${options.ordinal ? `ordinal${this.options.prepend}` : ""}${pluralCategory}`); + } + getSuffix(code, count, options = {}) { + const rule = this.getRule(code, options); + if (rule) { + return `${this.options.prepend}${options.ordinal ? `ordinal${this.options.prepend}` : ""}${rule.select(count)}`; + } + this.logger.warn(`no plural rule found for: ${code}`); + return this.getSuffix("dev", count, options); + } +} + +const deepFindWithDefaults = (data, defaultData, key, keySeparator = ".", ignoreJSONStructure = true) => { + let path = getPathWithDefaults(data, defaultData, key); + if (!path && ignoreJSONStructure && isString$1(key)) { + path = deepFind(data, key, keySeparator); + if (path === undefined) path = deepFind(defaultData, key, keySeparator); + } + return path; +}; +const regexSafe = (val) => val.replace(/\$/g, "$$$$"); +class Interpolator { + constructor(options = {}) { + this.logger = baseLogger.create("interpolator"); + this.options = options; + this.format = options?.interpolation?.format || ((value) => value); + this.init(options); + } + init(options = {}) { + if (!options.interpolation) + options.interpolation = { + escapeValue: true, + }; + const { + escape: escape$1, + escapeValue, + useRawValueToEscape, + prefix, + prefixEscaped, + suffix, + suffixEscaped, + formatSeparator, + unescapeSuffix, + unescapePrefix, + nestingPrefix, + nestingPrefixEscaped, + nestingSuffix, + nestingSuffixEscaped, + nestingOptionsSeparator, + maxReplaces, + alwaysFormat, + } = options.interpolation; + this.escape = escape$1 !== undefined ? escape$1 : escape; + this.escapeValue = escapeValue !== undefined ? escapeValue : true; + this.useRawValueToEscape = useRawValueToEscape !== undefined ? useRawValueToEscape : false; + this.prefix = prefix ? regexEscape(prefix) : prefixEscaped || "{{"; + this.suffix = suffix ? regexEscape(suffix) : suffixEscaped || "}}"; + this.formatSeparator = formatSeparator || ","; + this.unescapePrefix = unescapeSuffix ? "" : unescapePrefix || "-"; + this.unescapeSuffix = this.unescapePrefix ? "" : unescapeSuffix || ""; + this.nestingPrefix = nestingPrefix ? regexEscape(nestingPrefix) : nestingPrefixEscaped || regexEscape("$t("); + this.nestingSuffix = nestingSuffix ? regexEscape(nestingSuffix) : nestingSuffixEscaped || regexEscape(")"); + this.nestingOptionsSeparator = nestingOptionsSeparator || ","; + this.maxReplaces = maxReplaces || 1000; + this.alwaysFormat = alwaysFormat !== undefined ? alwaysFormat : false; + this.resetRegExp(); + } + reset() { + if (this.options) this.init(this.options); + } + resetRegExp() { + const getOrResetRegExp = (existingRegExp, pattern) => { + if (existingRegExp?.source === pattern) { + existingRegExp.lastIndex = 0; + return existingRegExp; + } + return new RegExp(pattern, "g"); + }; + this.regexp = getOrResetRegExp(this.regexp, `${this.prefix}(.+?)${this.suffix}`); + this.regexpUnescape = getOrResetRegExp(this.regexpUnescape, `${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`); + this.nestingRegexp = getOrResetRegExp(this.nestingRegexp, `${this.nestingPrefix}((?:[^()"']+|"[^"]*"|'[^']*'|\\((?:[^()]|"[^"]*"|'[^']*')*\\))*?)${this.nestingSuffix}`); + } + interpolate(str, data, lng, options) { + let match; + let value; + let replaces; + const defaultData = (this.options && this.options.interpolation && this.options.interpolation.defaultVariables) || {}; + const handleFormat = (key) => { + if (!key.includes(this.formatSeparator)) { + const path = deepFindWithDefaults(data, defaultData, key, this.options.keySeparator, this.options.ignoreJSONStructure); + return this.alwaysFormat ? + this.format(path, undefined, lng, { + ...options, + ...data, + interpolationkey: key, + }) + : path; + } + const p = key.split(this.formatSeparator); + const k = p.shift().trim(); + const f = p.join(this.formatSeparator).trim(); + return this.format(deepFindWithDefaults(data, defaultData, k, this.options.keySeparator, this.options.ignoreJSONStructure), f, lng, { + ...options, + ...data, + interpolationkey: k, + }); + }; + this.resetRegExp(); + const missingInterpolationHandler = options?.missingInterpolationHandler || this.options.missingInterpolationHandler; + const skipOnVariables = options?.interpolation?.skipOnVariables !== undefined ? options.interpolation.skipOnVariables : this.options.interpolation.skipOnVariables; + const todos = [ + { + regex: this.regexpUnescape, + safeValue: (val) => regexSafe(val), + }, + { + regex: this.regexp, + safeValue: (val) => (this.escapeValue ? regexSafe(this.escape(val)) : regexSafe(val)), + }, + ]; + todos.forEach((todo) => { + replaces = 0; + while ((match = todo.regex.exec(str))) { + const matchedVar = match[1].trim(); + value = handleFormat(matchedVar); + if (value === undefined) { + if (typeof missingInterpolationHandler === "function") { + const temp = missingInterpolationHandler(str, match, options); + value = isString$1(temp) ? temp : ""; + } else if (options && Object.prototype.hasOwnProperty.call(options, matchedVar)) { + value = ""; + } else if (skipOnVariables) { + value = match[0]; + continue; + } else { + this.logger.warn(`missed to pass in variable ${matchedVar} for interpolating ${str}`); + value = ""; + } + } else if (!isString$1(value) && !this.useRawValueToEscape) { + value = makeString(value); + } + const safeValue = todo.safeValue(value); + str = str.replace(match[0], safeValue); + if (skipOnVariables) { + todo.regex.lastIndex += value.length; + todo.regex.lastIndex -= match[0].length; + } else { + todo.regex.lastIndex = 0; + } + replaces++; + if (replaces >= this.maxReplaces) { + break; + } + } + }); + return str; + } + nest(str, fc, options = {}) { + let match; + let value; + let clonedOptions; + const handleHasOptions = (key, inheritedOptions) => { + const sep = this.nestingOptionsSeparator; + if (!key.includes(sep)) return key; + const c = key.split(new RegExp(`${regexEscape(sep)}[ ]*{`)); + let optionsString = `{${c[1]}`; + key = c[0]; + optionsString = this.interpolate(optionsString, clonedOptions); + const matchedSingleQuotes = optionsString.match(/'/g); + const matchedDoubleQuotes = optionsString.match(/"/g); + if (((matchedSingleQuotes?.length ?? 0) % 2 === 0 && !matchedDoubleQuotes) || (matchedDoubleQuotes?.length ?? 0) % 2 !== 0) { + optionsString = optionsString.replace(/'/g, '"'); + } + try { + clonedOptions = JSON.parse(optionsString); + if (inheritedOptions) + clonedOptions = { + ...inheritedOptions, + ...clonedOptions, + }; + } catch (e) { + this.logger.warn(`failed parsing options string in nesting for key ${key}`, e); + return `${key}${sep}${optionsString}`; + } + if (clonedOptions.defaultValue && clonedOptions.defaultValue.includes(this.prefix)) delete clonedOptions.defaultValue; + return key; + }; + while ((match = this.nestingRegexp.exec(str))) { + let formatters = []; + clonedOptions = { + ...options, + }; + clonedOptions = clonedOptions.replace && !isString$1(clonedOptions.replace) ? clonedOptions.replace : clonedOptions; + clonedOptions.applyPostProcessor = false; + delete clonedOptions.defaultValue; + const keyEndIndex = /{.*}/.test(match[1]) ? match[1].lastIndexOf("}") + 1 : match[1].indexOf(this.formatSeparator); + if (keyEndIndex !== -1) { + formatters = match[1] + .slice(keyEndIndex) + .split(this.formatSeparator) + .map((elem) => elem.trim()) + .filter(Boolean); + match[1] = match[1].slice(0, keyEndIndex); + } + value = fc(handleHasOptions.call(this, match[1].trim(), clonedOptions), clonedOptions); + if (value && match[0] === str && !isString$1(value)) return value; + if (!isString$1(value)) value = makeString(value); + if (!value) { + this.logger.warn(`missed to resolve ${match[1]} for nesting ${str}`); + value = ""; + } + if (formatters.length) { + value = formatters.reduce( + (v, f) => + this.format(v, f, options.lng, { + ...options, + interpolationkey: match[1].trim(), + }), + value.trim(), + ); + } + str = str.replace(match[0], value); + this.regexp.lastIndex = 0; + } + return str; + } +} + +const parseFormatStr = (formatStr) => { + let formatName = formatStr.toLowerCase().trim(); + const formatOptions = {}; + if (formatStr.includes("(")) { + const p = formatStr.split("("); + formatName = p[0].toLowerCase().trim(); + const optStr = p[1].slice(0, -1); + if (formatName === "currency" && !optStr.includes(":")) { + if (!formatOptions.currency) formatOptions.currency = optStr.trim(); + } else if (formatName === "relativetime" && !optStr.includes(":")) { + if (!formatOptions.range) formatOptions.range = optStr.trim(); + } else { + const opts = optStr.split(";"); + opts.forEach((opt) => { + if (opt) { + const [key, ...rest] = opt.split(":"); + const val = rest + .join(":") + .trim() + .replace(/^'+|'+$/g, ""); + const trimmedKey = key.trim(); + if (!formatOptions[trimmedKey]) formatOptions[trimmedKey] = val; + if (val === "false") formatOptions[trimmedKey] = false; + if (val === "true") formatOptions[trimmedKey] = true; + if (!isNaN(val)) formatOptions[trimmedKey] = parseInt(val, 10); + } + }); + } + } + return { + formatName, + formatOptions, + }; +}; +const createCachedFormatter = (fn) => { + const cache = {}; + return (v, l, o) => { + let optForCache = o; + if (o && o.interpolationkey && o.formatParams && o.formatParams[o.interpolationkey] && o[o.interpolationkey]) { + optForCache = { + ...optForCache, + [o.interpolationkey]: undefined, + }; + } + const key = l + JSON.stringify(optForCache); + let frm = cache[key]; + if (!frm) { + frm = fn(getCleanedCode(l), o); + cache[key] = frm; + } + return frm(v); + }; +}; +const createNonCachedFormatter = (fn) => (v, l, o) => fn(getCleanedCode(l), o)(v); +class Formatter { + constructor(options = {}) { + this.logger = baseLogger.create("formatter"); + this.options = options; + this.init(options); + } + init( + services, + options = { + interpolation: {}, + }, + ) { + this.formatSeparator = options.interpolation.formatSeparator || ","; + const cf = options.cacheInBuiltFormats ? createCachedFormatter : createNonCachedFormatter; + this.formats = { + number: cf((lng, opt) => { + const formatter = new Intl.NumberFormat(lng, { + ...opt, + }); + return (val) => formatter.format(val); + }), + currency: cf((lng, opt) => { + const formatter = new Intl.NumberFormat(lng, { + ...opt, + style: "currency", + }); + return (val) => formatter.format(val); + }), + datetime: cf((lng, opt) => { + const formatter = new Intl.DateTimeFormat(lng, { + ...opt, + }); + return (val) => formatter.format(val); + }), + relativetime: cf((lng, opt) => { + const formatter = new Intl.RelativeTimeFormat(lng, { + ...opt, + }); + return (val) => formatter.format(val, opt.range || "day"); + }), + list: cf((lng, opt) => { + const formatter = new Intl.ListFormat(lng, { + ...opt, + }); + return (val) => formatter.format(val); + }), + }; + } + add(name, fc) { + this.formats[name.toLowerCase().trim()] = fc; + } + addCached(name, fc) { + this.formats[name.toLowerCase().trim()] = createCachedFormatter(fc); + } + format(value, format, lng, options = {}) { + if (!format) return value; + if (value == null) return value; + const formats = format.split(this.formatSeparator); + if (formats.length > 1 && formats[0].indexOf("(") > 1 && !formats[0].includes(")") && formats.find((f) => f.includes(")"))) { + const lastIndex = formats.findIndex((f) => f.includes(")")); + formats[0] = [formats[0], ...formats.splice(1, lastIndex)].join(this.formatSeparator); + } + const result = formats.reduce((mem, f) => { + const { formatName, formatOptions } = parseFormatStr(f); + if (this.formats[formatName]) { + let formatted = mem; + try { + const valOptions = options?.formatParams?.[options.interpolationkey] || {}; + const l = valOptions.locale || valOptions.lng || options.locale || options.lng || lng; + formatted = this.formats[formatName](mem, l, { + ...formatOptions, + ...options, + ...valOptions, + }); + } catch (error) { + this.logger.warn(error); + } + return formatted; + } else { + this.logger.warn(`there was no format function for ${formatName}`); + } + return mem; + }, value); + return result; + } +} + +const removePending = (q, name) => { + if (q.pending[name] !== undefined) { + delete q.pending[name]; + q.pendingCount--; + } +}; +class Connector extends EventEmitter { + constructor(backend, store, services, options = {}) { + super(); + this.backend = backend; + this.store = store; + this.services = services; + this.languageUtils = services.languageUtils; + this.options = options; + this.logger = baseLogger.create("backendConnector"); + this.waitingReads = []; + this.maxParallelReads = options.maxParallelReads || 10; + this.readingCalls = 0; + this.maxRetries = options.maxRetries >= 0 ? options.maxRetries : 5; + this.retryTimeout = options.retryTimeout >= 1 ? options.retryTimeout : 350; + this.state = {}; + this.queue = []; + this.backend?.init?.(services, options.backend, options); + } + queueLoad(languages, namespaces, options, callback) { + const toLoad = {}; + const pending = {}; + const toLoadLanguages = {}; + const toLoadNamespaces = {}; + languages.forEach((lng) => { + let hasAllNamespaces = true; + namespaces.forEach((ns) => { + const name = `${lng}|${ns}`; + if (!options.reload && this.store.hasResourceBundle(lng, ns)) { + this.state[name] = 2; + } else if (this.state[name] < 0); + else if (this.state[name] === 1) { + if (pending[name] === undefined) pending[name] = true; + } else { + this.state[name] = 1; + hasAllNamespaces = false; + if (pending[name] === undefined) pending[name] = true; + if (toLoad[name] === undefined) toLoad[name] = true; + if (toLoadNamespaces[ns] === undefined) toLoadNamespaces[ns] = true; + } + }); + if (!hasAllNamespaces) toLoadLanguages[lng] = true; + }); + if (Object.keys(toLoad).length || Object.keys(pending).length) { + this.queue.push({ + pending, + pendingCount: Object.keys(pending).length, + loaded: {}, + errors: [], + callback, + }); + } + return { + toLoad: Object.keys(toLoad), + pending: Object.keys(pending), + toLoadLanguages: Object.keys(toLoadLanguages), + toLoadNamespaces: Object.keys(toLoadNamespaces), + }; + } + loaded(name, err, data) { + const s = name.split("|"); + const lng = s[0]; + const ns = s[1]; + if (err) this.emit("failedLoading", lng, ns, err); + if (!err && data) { + this.store.addResourceBundle(lng, ns, data, undefined, undefined, { + skipCopy: true, + }); + } + this.state[name] = err ? -1 : 2; + if (err && data) this.state[name] = 0; + const loaded = {}; + this.queue.forEach((q) => { + pushPath(q.loaded, [lng], ns); + removePending(q, name); + if (err) q.errors.push(err); + if (q.pendingCount === 0 && !q.done) { + Object.keys(q.loaded).forEach((l) => { + if (!loaded[l]) loaded[l] = {}; + const loadedKeys = q.loaded[l]; + if (loadedKeys.length) { + loadedKeys.forEach((n) => { + if (loaded[l][n] === undefined) loaded[l][n] = true; + }); + } + }); + q.done = true; + if (q.errors.length) { + q.callback(q.errors); + } else { + q.callback(); + } + } + }); + this.emit("loaded", loaded); + this.queue = this.queue.filter((q) => !q.done); + } + read(lng, ns, fcName, tried = 0, wait = this.retryTimeout, callback) { + if (!lng.length) return callback(null, {}); + if (this.readingCalls >= this.maxParallelReads) { + this.waitingReads.push({ + lng, + ns, + fcName, + tried, + wait, + callback, + }); + return; + } + this.readingCalls++; + const resolver = (err, data) => { + this.readingCalls--; + if (this.waitingReads.length > 0) { + const next = this.waitingReads.shift(); + this.read(next.lng, next.ns, next.fcName, next.tried, next.wait, next.callback); + } + if (err && data && tried < this.maxRetries) { + setTimeout(() => { + this.read(lng, ns, fcName, tried + 1, wait * 2, callback); + }, wait); + return; + } + callback(err, data); + }; + const fc = this.backend[fcName].bind(this.backend); + if (fc.length === 2) { + try { + const r = fc(lng, ns); + if (r && typeof r.then === "function") { + r.then((data) => resolver(null, data)).catch(resolver); + } else { + resolver(null, r); + } + } catch (err) { + resolver(err); + } + return; + } + return fc(lng, ns, resolver); + } + prepareLoading(languages, namespaces, options = {}, callback) { + if (!this.backend) { + this.logger.warn("No backend was added via i18next.use. Will not load resources."); + return callback && callback(); + } + if (isString$1(languages)) languages = this.languageUtils.toResolveHierarchy(languages); + if (isString$1(namespaces)) namespaces = [namespaces]; + const toLoad = this.queueLoad(languages, namespaces, options, callback); + if (!toLoad.toLoad.length) { + if (!toLoad.pending.length) callback(); + return null; + } + toLoad.toLoad.forEach((name) => { + this.loadOne(name); + }); + } + load(languages, namespaces, callback) { + this.prepareLoading(languages, namespaces, {}, callback); + } + reload(languages, namespaces, callback) { + this.prepareLoading( + languages, + namespaces, + { + reload: true, + }, + callback, + ); + } + loadOne(name, prefix = "") { + const s = name.split("|"); + const lng = s[0]; + const ns = s[1]; + this.read(lng, ns, "read", undefined, undefined, (err, data) => { + if (err) this.logger.warn(`${prefix}loading namespace ${ns} for language ${lng} failed`, err); + if (!err && data) this.logger.log(`${prefix}loaded namespace ${ns} for language ${lng}`, data); + this.loaded(name, err, data); + }); + } + saveMissing(languages, namespace, key, fallbackValue, isUpdate, options = {}, clb = () => {}) { + if (this.services?.utils?.hasLoadedNamespace && !this.services?.utils?.hasLoadedNamespace(namespace)) { + this.logger.warn( + `did not save key "${key}" as the namespace "${namespace}" was not yet loaded`, + "This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!", + ); + return; + } + if (key === undefined || key === null || key === "") return; + if (this.backend?.create) { + const opts = { + ...options, + isUpdate, + }; + const fc = this.backend.create.bind(this.backend); + if (fc.length < 6) { + try { + let r; + if (fc.length === 5) { + r = fc(languages, namespace, key, fallbackValue, opts); + } else { + r = fc(languages, namespace, key, fallbackValue); + } + if (r && typeof r.then === "function") { + r.then((data) => clb(null, data)).catch(clb); + } else { + clb(null, r); + } + } catch (err) { + clb(err); + } + } else { + fc(languages, namespace, key, fallbackValue, clb, opts); + } + } + if (!languages || !languages[0]) return; + this.store.addResource(languages[0], namespace, key, fallbackValue); + } +} + +const get = () => ({ + debug: false, + initAsync: true, + ns: ["translation"], + defaultNS: ["translation"], + fallbackLng: ["dev"], + fallbackNS: false, + supportedLngs: false, + nonExplicitSupportedLngs: false, + load: "all", + preload: false, + keySeparator: ".", + nsSeparator: ":", + pluralSeparator: "_", + contextSeparator: "_", + partialBundledLanguages: false, + saveMissing: false, + updateMissing: false, + saveMissingTo: "fallback", + saveMissingPlurals: true, + missingKeyHandler: false, + missingInterpolationHandler: false, + postProcess: false, + postProcessPassResolved: false, + returnNull: false, + returnEmptyString: true, + returnObjects: false, + joinArrays: false, + returnedObjectHandler: false, + parseMissingKeyHandler: false, + appendNamespaceToMissingKey: false, + appendNamespaceToCIMode: false, + overloadTranslationOptionHandler: (args) => { + let ret = {}; + if (typeof args[1] === "object") ret = args[1]; + if (isString$1(args[1])) ret.defaultValue = args[1]; + if (isString$1(args[2])) ret.tDescription = args[2]; + if (typeof args[2] === "object" || typeof args[3] === "object") { + const options = args[3] || args[2]; + Object.keys(options).forEach((key) => { + ret[key] = options[key]; + }); + } + return ret; + }, + interpolation: { + escapeValue: true, + prefix: "{{", + suffix: "}}", + formatSeparator: ",", + unescapePrefix: "-", + nestingPrefix: "$t(", + nestingSuffix: ")", + nestingOptionsSeparator: ",", + maxReplaces: 1000, + skipOnVariables: true, + }, + cacheInBuiltFormats: true, +}); +const transformOptions = (options) => { + if (isString$1(options.ns)) options.ns = [options.ns]; + if (isString$1(options.fallbackLng)) options.fallbackLng = [options.fallbackLng]; + if (isString$1(options.fallbackNS)) options.fallbackNS = [options.fallbackNS]; + if (options.supportedLngs && !options.supportedLngs.includes("cimode")) { + options.supportedLngs = options.supportedLngs.concat(["cimode"]); + } + return options; +}; + +const noop$2 = () => {}; +const bindMemberFunctions = (inst) => { + const mems = Object.getOwnPropertyNames(Object.getPrototypeOf(inst)); + mems.forEach((mem) => { + if (typeof inst[mem] === "function") { + inst[mem] = inst[mem].bind(inst); + } + }); +}; +class I18n extends EventEmitter { + constructor(options = {}, callback) { + super(); + this.options = transformOptions(options); + this.services = {}; + this.logger = baseLogger; + this.modules = { + external: [], + }; + bindMemberFunctions(this); + if (callback && !this.isInitialized && !options.isClone) { + if (!this.options.initAsync) { + this.init(options, callback); + return this; + } + setTimeout(() => { + this.init(options, callback); + }, 0); + } + } + init(options = {}, callback) { + this.isInitializing = true; + if (typeof options === "function") { + callback = options; + options = {}; + } + if (options.defaultNS == null && options.ns) { + if (isString$1(options.ns)) { + options.defaultNS = options.ns; + } else if (!options.ns.includes("translation")) { + options.defaultNS = options.ns[0]; + } + } + const defOpts = get(); + this.options = { + ...defOpts, + ...this.options, + ...transformOptions(options), + }; + this.options.interpolation = { + ...defOpts.interpolation, + ...this.options.interpolation, + }; + if (options.keySeparator !== undefined) { + this.options.userDefinedKeySeparator = options.keySeparator; + } + if (options.nsSeparator !== undefined) { + this.options.userDefinedNsSeparator = options.nsSeparator; + } + if (typeof this.options.overloadTranslationOptionHandler !== "function") { + this.options.overloadTranslationOptionHandler = defOpts.overloadTranslationOptionHandler; + } + const createClassOnDemand = (ClassOrObject) => { + if (!ClassOrObject) return null; + if (typeof ClassOrObject === "function") return new ClassOrObject(); + return ClassOrObject; + }; + if (!this.options.isClone) { + if (this.modules.logger) { + baseLogger.init(createClassOnDemand(this.modules.logger), this.options); + } else { + baseLogger.init(null, this.options); + } + let formatter; + if (this.modules.formatter) { + formatter = this.modules.formatter; + } else { + formatter = Formatter; + } + const lu = new LanguageUtil(this.options); + this.store = new ResourceStore(this.options.resources, this.options); + const s = this.services; + s.logger = baseLogger; + s.resourceStore = this.store; + s.languageUtils = lu; + s.pluralResolver = new PluralResolver(lu, { + prepend: this.options.pluralSeparator, + }); + if (formatter) { + s.formatter = createClassOnDemand(formatter); + if (s.formatter.init) s.formatter.init(s, this.options); + this.options.interpolation.format = s.formatter.format.bind(s.formatter); + } + s.interpolator = new Interpolator(this.options); + s.utils = { + hasLoadedNamespace: this.hasLoadedNamespace.bind(this), + }; + s.backendConnector = new Connector(createClassOnDemand(this.modules.backend), s.resourceStore, s, this.options); + s.backendConnector.on("*", (event, ...args) => { + this.emit(event, ...args); + }); + if (this.modules.languageDetector) { + s.languageDetector = createClassOnDemand(this.modules.languageDetector); + if (s.languageDetector.init) s.languageDetector.init(s, this.options.detection, this.options); + } + if (this.modules.i18nFormat) { + s.i18nFormat = createClassOnDemand(this.modules.i18nFormat); + if (s.i18nFormat.init) s.i18nFormat.init(this); + } + this.translator = new Translator(this.services, this.options); + this.translator.on("*", (event, ...args) => { + this.emit(event, ...args); + }); + this.modules.external.forEach((m) => { + if (m.init) m.init(this); + }); + } + this.format = this.options.interpolation.format; + if (!callback) callback = noop$2; + if (this.options.fallbackLng && !this.services.languageDetector && !this.options.lng) { + const codes = this.services.languageUtils.getFallbackCodes(this.options.fallbackLng); + if (codes.length > 0 && codes[0] !== "dev") this.options.lng = codes[0]; + } + if (!this.services.languageDetector && !this.options.lng) { + this.logger.warn("init: no languageDetector is used and no lng is defined"); + } + const storeApi = ["getResource", "hasResourceBundle", "getResourceBundle", "getDataByLanguage"]; + storeApi.forEach((fcName) => { + this[fcName] = (...args) => this.store[fcName](...args); + }); + const storeApiChained = ["addResource", "addResources", "addResourceBundle", "removeResourceBundle"]; + storeApiChained.forEach((fcName) => { + this[fcName] = (...args) => { + this.store[fcName](...args); + return this; + }; + }); + const deferred = defer(); + const load = () => { + const finish = (err, t) => { + this.isInitializing = false; + if (this.isInitialized && !this.initializedStoreOnce) this.logger.warn("init: i18next is already initialized. You should call init just once!"); + this.isInitialized = true; + if (!this.options.isClone) this.logger.log("initialized", this.options); + this.emit("initialized", this.options); + deferred.resolve(t); + callback(err, t); + }; + if (this.languages && !this.isInitialized) return finish(null, this.t.bind(this)); + this.changeLanguage(this.options.lng, finish); + }; + if (this.options.resources || !this.options.initAsync) { + load(); + } else { + setTimeout(load, 0); + } + return deferred; + } + loadResources(language, callback = noop$2) { + let usedCallback = callback; + const usedLng = isString$1(language) ? language : this.language; + if (typeof language === "function") usedCallback = language; + if (!this.options.resources || this.options.partialBundledLanguages) { + if (usedLng?.toLowerCase() === "cimode" && (!this.options.preload || this.options.preload.length === 0)) return usedCallback(); + const toLoad = []; + const append = (lng) => { + if (!lng) return; + if (lng === "cimode") return; + const lngs = this.services.languageUtils.toResolveHierarchy(lng); + lngs.forEach((l) => { + if (l === "cimode") return; + if (!toLoad.includes(l)) toLoad.push(l); + }); + }; + if (!usedLng) { + const fallbacks = this.services.languageUtils.getFallbackCodes(this.options.fallbackLng); + fallbacks.forEach((l) => append(l)); + } else { + append(usedLng); + } + this.options.preload?.forEach?.((l) => append(l)); + this.services.backendConnector.load(toLoad, this.options.ns, (e) => { + if (!e && !this.resolvedLanguage && this.language) this.setResolvedLanguage(this.language); + usedCallback(e); + }); + } else { + usedCallback(null); + } + } + reloadResources(lngs, ns, callback) { + const deferred = defer(); + if (typeof lngs === "function") { + callback = lngs; + lngs = undefined; + } + if (typeof ns === "function") { + callback = ns; + ns = undefined; + } + if (!lngs) lngs = this.languages; + if (!ns) ns = this.options.ns; + if (!callback) callback = noop$2; + this.services.backendConnector.reload(lngs, ns, (err) => { + deferred.resolve(); + callback(err); + }); + return deferred; + } + use(module) { + if (!module) throw new Error("You are passing an undefined module! Please check the object you are passing to i18next.use()"); + if (!module.type) throw new Error("You are passing a wrong module! Please check the object you are passing to i18next.use()"); + if (module.type === "backend") { + this.modules.backend = module; + } + if (module.type === "logger" || (module.log && module.warn && module.error)) { + this.modules.logger = module; + } + if (module.type === "languageDetector") { + this.modules.languageDetector = module; + } + if (module.type === "i18nFormat") { + this.modules.i18nFormat = module; + } + if (module.type === "postProcessor") { + postProcessor.addPostProcessor(module); + } + if (module.type === "formatter") { + this.modules.formatter = module; + } + if (module.type === "3rdParty") { + this.modules.external.push(module); + } + return this; + } + setResolvedLanguage(l) { + if (!l || !this.languages) return; + if (["cimode", "dev"].includes(l)) return; + for (let li = 0; li < this.languages.length; li++) { + const lngInLngs = this.languages[li]; + if (["cimode", "dev"].includes(lngInLngs)) continue; + if (this.store.hasLanguageSomeTranslations(lngInLngs)) { + this.resolvedLanguage = lngInLngs; + break; + } + } + if (!this.resolvedLanguage && !this.languages.includes(l) && this.store.hasLanguageSomeTranslations(l)) { + this.resolvedLanguage = l; + this.languages.unshift(l); + } + } + changeLanguage(lng, callback) { + this.isLanguageChangingTo = lng; + const deferred = defer(); + this.emit("languageChanging", lng); + const setLngProps = (l) => { + this.language = l; + this.languages = this.services.languageUtils.toResolveHierarchy(l); + this.resolvedLanguage = undefined; + this.setResolvedLanguage(l); + }; + const done = (err, l) => { + if (l) { + if (this.isLanguageChangingTo === lng) { + setLngProps(l); + this.translator.changeLanguage(l); + this.isLanguageChangingTo = undefined; + this.emit("languageChanged", l); + this.logger.log("languageChanged", l); + } + } else { + this.isLanguageChangingTo = undefined; + } + deferred.resolve((...args) => this.t(...args)); + if (callback) callback(err, (...args) => this.t(...args)); + }; + const setLng = (lngs) => { + if (!lng && !lngs && this.services.languageDetector) lngs = []; + const fl = isString$1(lngs) ? lngs : lngs && lngs[0]; + const l = this.store.hasLanguageSomeTranslations(fl) ? fl : this.services.languageUtils.getBestMatchFromCodes(isString$1(lngs) ? [lngs] : lngs); + if (l) { + if (!this.language) { + setLngProps(l); + } + if (!this.translator.language) this.translator.changeLanguage(l); + this.services.languageDetector?.cacheUserLanguage?.(l); + } + this.loadResources(l, (err) => { + done(err, l); + }); + }; + if (!lng && this.services.languageDetector && !this.services.languageDetector.async) { + setLng(this.services.languageDetector.detect()); + } else if (!lng && this.services.languageDetector && this.services.languageDetector.async) { + if (this.services.languageDetector.detect.length === 0) { + this.services.languageDetector.detect().then(setLng); + } else { + this.services.languageDetector.detect(setLng); + } + } else { + setLng(lng); + } + return deferred; + } + getFixedT(lng, ns, keyPrefix) { + const fixedT = (key, opts, ...rest) => { + let o; + if (typeof opts !== "object") { + o = this.options.overloadTranslationOptionHandler([key, opts].concat(rest)); + } else { + o = { + ...opts, + }; + } + o.lng = o.lng || fixedT.lng; + o.lngs = o.lngs || fixedT.lngs; + o.ns = o.ns || fixedT.ns; + if (o.keyPrefix !== "") o.keyPrefix = o.keyPrefix || keyPrefix || fixedT.keyPrefix; + const selectorOpts = { + ...this.options, + ...o, + }; + if (typeof o.keyPrefix === "function") o.keyPrefix = keysFromSelector(o.keyPrefix, selectorOpts); + const keySeparator = this.options.keySeparator || "."; + let resultKey; + if (o.keyPrefix && Array.isArray(key)) { + resultKey = key.map((k) => { + if (typeof k === "function") k = keysFromSelector(k, selectorOpts); + return `${o.keyPrefix}${keySeparator}${k}`; + }); + } else { + if (typeof key === "function") key = keysFromSelector(key, selectorOpts); + resultKey = o.keyPrefix ? `${o.keyPrefix}${keySeparator}${key}` : key; + } + return this.t(resultKey, o); + }; + if (isString$1(lng)) { + fixedT.lng = lng; + } else { + fixedT.lngs = lng; + } + fixedT.ns = ns; + fixedT.keyPrefix = keyPrefix; + return fixedT; + } + t(...args) { + return this.translator?.translate(...args); + } + exists(...args) { + return this.translator?.exists(...args); + } + setDefaultNamespace(ns) { + this.options.defaultNS = ns; + } + hasLoadedNamespace(ns, options = {}) { + if (!this.isInitialized) { + this.logger.warn("hasLoadedNamespace: i18next was not initialized", this.languages); + return false; + } + if (!this.languages || !this.languages.length) { + this.logger.warn("hasLoadedNamespace: i18n.languages were undefined or empty", this.languages); + return false; + } + const lng = options.lng || this.resolvedLanguage || this.languages[0]; + const fallbackLng = this.options ? this.options.fallbackLng : false; + const lastLng = this.languages[this.languages.length - 1]; + if (lng.toLowerCase() === "cimode") return true; + const loadNotPending = (l, n) => { + const loadState = this.services.backendConnector.state[`${l}|${n}`]; + return loadState === -1 || loadState === 0 || loadState === 2; + }; + if (options.precheck) { + const preResult = options.precheck(this, loadNotPending); + if (preResult !== undefined) return preResult; + } + if (this.hasResourceBundle(lng, ns)) return true; + if (!this.services.backendConnector.backend || (this.options.resources && !this.options.partialBundledLanguages)) return true; + if (loadNotPending(lng, ns) && (!fallbackLng || loadNotPending(lastLng, ns))) return true; + return false; + } + loadNamespaces(ns, callback) { + const deferred = defer(); + if (!this.options.ns) { + if (callback) callback(); + return Promise.resolve(); + } + if (isString$1(ns)) ns = [ns]; + ns.forEach((n) => { + if (!this.options.ns.includes(n)) this.options.ns.push(n); + }); + this.loadResources((err) => { + deferred.resolve(); + if (callback) callback(err); + }); + return deferred; + } + loadLanguages(lngs, callback) { + const deferred = defer(); + if (isString$1(lngs)) lngs = [lngs]; + const preloaded = this.options.preload || []; + const newLngs = lngs.filter((lng) => !preloaded.includes(lng) && this.services.languageUtils.isSupportedCode(lng)); + if (!newLngs.length) { + if (callback) callback(); + return Promise.resolve(); + } + this.options.preload = preloaded.concat(newLngs); + this.loadResources((err) => { + deferred.resolve(); + if (callback) callback(err); + }); + return deferred; + } + dir(lng) { + if (!lng) lng = this.resolvedLanguage || (this.languages?.length > 0 ? this.languages[0] : this.language); + if (!lng) return "rtl"; + try { + const l = new Intl.Locale(lng); + if (l && l.getTextInfo) { + const ti = l.getTextInfo(); + if (ti && ti.direction) return ti.direction; + } + } catch (e) {} + const rtlLngs = [ + "ar", + "shu", + "sqr", + "ssh", + "xaa", + "yhd", + "yud", + "aao", + "abh", + "abv", + "acm", + "acq", + "acw", + "acx", + "acy", + "adf", + "ads", + "aeb", + "aec", + "afb", + "ajp", + "apc", + "apd", + "arb", + "arq", + "ars", + "ary", + "arz", + "auz", + "avl", + "ayh", + "ayl", + "ayn", + "ayp", + "bbz", + "pga", + "he", + "iw", + "ps", + "pbt", + "pbu", + "pst", + "prp", + "prd", + "ug", + "ur", + "ydd", + "yds", + "yih", + "ji", + "yi", + "hbo", + "men", + "xmn", + "fa", + "jpr", + "peo", + "pes", + "prs", + "dv", + "sam", + "ckb", + ]; + const languageUtils = this.services?.languageUtils || new LanguageUtil(get()); + if (lng.toLowerCase().indexOf("-latn") > 1) return "ltr"; + return rtlLngs.includes(languageUtils.getLanguagePartFromCode(lng)) || lng.toLowerCase().indexOf("-arab") > 1 ? "rtl" : "ltr"; + } + static createInstance(options = {}, callback) { + const instance = new I18n(options, callback); + instance.createInstance = I18n.createInstance; + return instance; + } + cloneInstance(options = {}, callback = noop$2) { + const forkResourceStore = options.forkResourceStore; + if (forkResourceStore) delete options.forkResourceStore; + const mergedOptions = { + ...this.options, + ...options, + ...{ + isClone: true, + }, + }; + const clone = new I18n(mergedOptions); + if (options.debug !== undefined || options.prefix !== undefined) { + clone.logger = clone.logger.clone(options); + } + const membersToCopy = ["store", "services", "language"]; + membersToCopy.forEach((m) => { + clone[m] = this[m]; + }); + clone.services = { + ...this.services, + }; + clone.services.utils = { + hasLoadedNamespace: clone.hasLoadedNamespace.bind(clone), + }; + if (forkResourceStore) { + const clonedData = Object.keys(this.store.data).reduce((prev, l) => { + prev[l] = { + ...this.store.data[l], + }; + prev[l] = Object.keys(prev[l]).reduce((acc, n) => { + acc[n] = { + ...prev[l][n], + }; + return acc; + }, prev[l]); + return prev; + }, {}); + clone.store = new ResourceStore(clonedData, mergedOptions); + clone.services.resourceStore = clone.store; + } + if (options.interpolation) { + const defOpts = get(); + const mergedInterpolation = { + ...defOpts.interpolation, + ...this.options.interpolation, + ...options.interpolation, + }; + const mergedForInterpolator = { + ...mergedOptions, + interpolation: mergedInterpolation, + }; + clone.services.interpolator = new Interpolator(mergedForInterpolator); + } + clone.translator = new Translator(clone.services, mergedOptions); + clone.translator.on("*", (event, ...args) => { + clone.emit(event, ...args); + }); + clone.init(mergedOptions, callback); + clone.translator.options = mergedOptions; + clone.translator.backendConnector.services.utils = { + hasLoadedNamespace: clone.hasLoadedNamespace.bind(clone), + }; + return clone; + } + toJSON() { + return { + options: this.options, + store: this.store, + language: this.language, + languages: this.languages, + resolvedLanguage: this.resolvedLanguage, + }; + } +} +const instance = I18n.createInstance(); + +instance.createInstance; +instance.dir; +instance.init; +instance.loadResources; +instance.reloadResources; +instance.use; +instance.changeLanguage; +instance.getFixedT; +instance.t; +instance.exists; +instance.setDefaultNamespace; +instance.hasLoadedNamespace; +instance.loadNamespaces; +instance.loadLanguages; + +const warn = (i18n, code, msg, rest) => { + const args = [ + msg, + { + code, + ...(rest || {}), + }, + ]; + if (i18n?.services?.logger?.forward) { + return i18n.services.logger.forward(args, "warn", "react-i18next::", true); + } + if (isString(args[0])) args[0] = `react-i18next:: ${args[0]}`; + if (i18n?.services?.logger?.warn) { + i18n.services.logger.warn(...args); + } else if (console?.warn) { + console.warn(...args); + } +}; +const alreadyWarned = {}; +const warnOnce = (i18n, code, msg, rest) => { + if (isString(msg) && alreadyWarned[msg]) return; + if (isString(msg)) alreadyWarned[msg] = new Date(); + warn(i18n, code, msg, rest); +}; +const loadedClb = (i18n, cb) => () => { + if (i18n.isInitialized) { + cb(); + } else { + const initialized = () => { + setTimeout(() => { + i18n.off("initialized", initialized); + }, 0); + cb(); + }; + i18n.on("initialized", initialized); + } +}; +const loadNamespaces = (i18n, ns, cb) => { + i18n.loadNamespaces(ns, loadedClb(i18n, cb)); +}; +const loadLanguages = (i18n, lng, ns, cb) => { + if (isString(ns)) ns = [ns]; + if (i18n.options.preload && i18n.options.preload.indexOf(lng) > -1) return loadNamespaces(i18n, ns, cb); + ns.forEach((n) => { + if (i18n.options.ns.indexOf(n) < 0) i18n.options.ns.push(n); + }); + i18n.loadLanguages(lng, loadedClb(i18n, cb)); +}; +const hasLoadedNamespace = (ns, i18n, options = {}) => { + if (!i18n.languages || !i18n.languages.length) { + warnOnce(i18n, "NO_LANGUAGES", "i18n.languages were undefined or empty", { + languages: i18n.languages, + }); + return true; + } + return i18n.hasLoadedNamespace(ns, { + lng: options.lng, + precheck: (i18nInstance, loadNotPending) => { + if ( + options.bindI18n && + options.bindI18n.indexOf("languageChanging") > -1 && + i18nInstance.services.backendConnector.backend && + i18nInstance.isLanguageChangingTo && + !loadNotPending(i18nInstance.isLanguageChangingTo, ns) + ) + return false; + }, + }); +}; +const isString = (obj) => typeof obj === "string"; +const isObject$1 = (obj) => typeof obj === "object" && obj !== null; + +const matchHtmlEntity = /&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34|nbsp|#160|copy|#169|reg|#174|hellip|#8230|#x2F|#47);/g; +const htmlEntities = { + "&": "&", + "&": "&", + "<": "<", + "<": "<", + ">": ">", + ">": ">", + "'": "'", + "'": "'", + """: '"', + """: '"', + " ": " ", + " ": " ", + "©": "©", + "©": "©", + "®": "®", + "®": "®", + "…": "…", + "…": "…", + "/": "/", + "/": "/", +}; +const unescapeHtmlEntity = (m) => htmlEntities[m]; +const unescape = (text) => text.replace(matchHtmlEntity, unescapeHtmlEntity); + +let defaultOptions$2 = { + bindI18n: "languageChanged", + bindI18nStore: "", + transEmptyNodeValue: "", + transSupportBasicHtmlNodes: true, + transWrapTextNodes: "", + transKeepBasicHtmlNodesFor: ["br", "strong", "i", "p"], + useSuspense: true, + unescape, + transDefaultProps: undefined, +}; +const setDefaults = (options = {}) => { + defaultOptions$2 = { + ...defaultOptions$2, + ...options, + }; +}; +const getDefaults = () => defaultOptions$2; + +let i18nInstance; +const setI18n = (instance) => { + i18nInstance = instance; +}; +const getI18n = () => i18nInstance; + +const initReactI18next = { + type: "3rdParty", + init(instance) { + setDefaults(instance.options.react); + setI18n(instance); + }, +}; + +const { createContext: createContext$3 } = await importShared("react"); +const I18nContext = createContext$3(); +class ReportNamespaces { + constructor() { + this.usedNamespaces = {}; + } + addUsedNamespaces(namespaces) { + namespaces.forEach((ns) => { + if (!this.usedNamespaces[ns]) this.usedNamespaces[ns] = true; + }); + } + getUsedNamespaces() { + return Object.keys(this.usedNamespaces); + } +} + +const { useContext: useContext$3, useCallback: useCallback$4, useMemo: useMemo$2, useEffect: useEffect$3, useRef: useRef$3, useState: useState$3 } = await importShared("react"); +const notReadyT = (k, optsOrDefaultValue) => { + if (isString(optsOrDefaultValue)) return optsOrDefaultValue; + if (isObject$1(optsOrDefaultValue) && isString(optsOrDefaultValue.defaultValue)) return optsOrDefaultValue.defaultValue; + if (typeof k === "function") return ""; + if (Array.isArray(k)) { + const last = k[k.length - 1]; + return typeof last === "function" ? "" : last; + } + return k; +}; +const notReadySnapshot = { + t: notReadyT, + ready: false, +}; +const dummySubscribe = () => () => {}; +const useTranslation = (ns, props = {}) => { + const { i18n: i18nFromProps } = props; + const { i18n: i18nFromContext, defaultNS: defaultNSFromContext } = useContext$3(I18nContext) || {}; + const i18n = i18nFromProps || i18nFromContext || getI18n(); + if (i18n && !i18n.reportNamespaces) i18n.reportNamespaces = new ReportNamespaces(); + if (!i18n) { + warnOnce(i18n, "NO_I18NEXT_INSTANCE", "useTranslation: You will need to pass in an i18next instance by using initReactI18next"); + } + const i18nOptions = useMemo$2( + () => ({ + ...getDefaults(), + ...i18n?.options?.react, + ...props, + }), + [i18n, props], + ); + const { useSuspense, keyPrefix } = i18nOptions; + const nsOrContext = ns || defaultNSFromContext || i18n?.options?.defaultNS; + const unstableNamespaces = isString(nsOrContext) ? [nsOrContext] : nsOrContext || ["translation"]; + const namespaces = useMemo$2(() => unstableNamespaces, unstableNamespaces); + i18n?.reportNamespaces?.addUsedNamespaces?.(namespaces); + const revisionRef = useRef$3(0); + const subscribe = useCallback$4( + (callback) => { + if (!i18n) return dummySubscribe; + const { bindI18n, bindI18nStore } = i18nOptions; + const wrappedCallback = () => { + revisionRef.current += 1; + callback(); + }; + if (bindI18n) i18n.on(bindI18n, wrappedCallback); + if (bindI18nStore) i18n.store.on(bindI18nStore, wrappedCallback); + return () => { + if (bindI18n) bindI18n.split(" ").forEach((e) => i18n.off(e, wrappedCallback)); + if (bindI18nStore) bindI18nStore.split(" ").forEach((e) => i18n.store.off(e, wrappedCallback)); + }; + }, + [i18n, i18nOptions], + ); + const snapshotRef = useRef$3(); + const getSnapshot = useCallback$4(() => { + if (!i18n) { + return notReadySnapshot; + } + const calculatedReady = !!(i18n.isInitialized || i18n.initializedStoreOnce) && namespaces.every((n) => hasLoadedNamespace(n, i18n, i18nOptions)); + const currentLng = props.lng || i18n.language; + const currentRevision = revisionRef.current; + const lastSnapshot = snapshotRef.current; + if (lastSnapshot && lastSnapshot.ready === calculatedReady && lastSnapshot.lng === currentLng && lastSnapshot.keyPrefix === keyPrefix && lastSnapshot.revision === currentRevision) { + return lastSnapshot; + } + const calculatedT = i18n.getFixedT(currentLng, i18nOptions.nsMode === "fallback" ? namespaces : namespaces[0], keyPrefix); + const newSnapshot = { + t: calculatedT, + ready: calculatedReady, + lng: currentLng, + keyPrefix, + revision: currentRevision, + }; + snapshotRef.current = newSnapshot; + return newSnapshot; + }, [i18n, namespaces, keyPrefix, i18nOptions, props.lng]); + const [loadCount, setLoadCount] = useState$3(0); + const { t, ready } = shimExports.useSyncExternalStore(subscribe, getSnapshot, getSnapshot); + useEffect$3(() => { + if (i18n && !ready && !useSuspense) { + const onLoaded = () => setLoadCount((c) => c + 1); + if (props.lng) { + loadLanguages(i18n, props.lng, namespaces, onLoaded); + } else { + loadNamespaces(i18n, namespaces, onLoaded); + } + } + }, [i18n, props.lng, namespaces, ready, useSuspense, loadCount]); + const finalI18n = i18n || {}; + const wrapperRef = useRef$3(null); + const wrapperLangRef = useRef$3(); + const createI18nWrapper = (original) => { + const descriptors = Object.getOwnPropertyDescriptors(original); + if (descriptors.__original) delete descriptors.__original; + const wrapper = Object.create(Object.getPrototypeOf(original), descriptors); + if (!Object.prototype.hasOwnProperty.call(wrapper, "__original")) { + try { + Object.defineProperty(wrapper, "__original", { + value: original, + writable: false, + enumerable: false, + configurable: false, + }); + } catch (_) {} + } + return wrapper; + }; + const ret = useMemo$2(() => { + const original = finalI18n; + const lang = original?.language; + let i18nWrapper = original; + if (original) { + if (wrapperRef.current && wrapperRef.current.__original === original) { + if (wrapperLangRef.current !== lang) { + i18nWrapper = createI18nWrapper(original); + wrapperRef.current = i18nWrapper; + wrapperLangRef.current = lang; + } else { + i18nWrapper = wrapperRef.current; + } + } else { + i18nWrapper = createI18nWrapper(original); + wrapperRef.current = i18nWrapper; + wrapperLangRef.current = lang; + } + } + const effectiveT = + !ready && !useSuspense ? + (...args) => { + warnOnce(i18n, "USE_T_BEFORE_READY", "useTranslation: t was called before ready. When using useSuspense: false, make sure to check the ready flag before using t."); + return t(...args); + } + : t; + const arr = [effectiveT, i18nWrapper, ready]; + arr.t = effectiveT; + arr.i18n = i18nWrapper; + arr.ready = ready; + return arr; + }, [t, finalI18n, ready, finalI18n.resolvedLanguage, finalI18n.language, finalI18n.languages]); + if (i18n && useSuspense && !ready) { + throw new Promise((resolve) => { + const onLoaded = () => resolve(); + if (props.lng) { + loadLanguages(i18n, props.lng, namespaces, onLoaded); + } else { + loadNamespaces(i18n, namespaces, onLoaded); + } + }); + } + return ret; +}; + +/** + * @typedef Options + * Configuration. + * @property {boolean | null | undefined} [jsx=false] + * Support JSX identifiers (default: `false`). + */ + +const nameRe = /^[$_\p{ID_Start}][$_\u{200C}\u{200D}\p{ID_Continue}]*$/u; +const nameReJsx = /^[$_\p{ID_Start}][-$_\u{200C}\u{200D}\p{ID_Continue}]*$/u; + +/** @type {Options} */ +const emptyOptions = {}; + +/** + * Checks if the given value is a valid identifier name. + * + * @param {string} name + * Identifier to check. + * @param {Options | null | undefined} [options] + * Configuration (optional). + * @returns {boolean} + * Whether `name` can be an identifier. + */ +function name(name, options) { + const settings = emptyOptions; + const re = settings.jsx ? nameReJsx : nameRe; + return re.test(name); +} + +var cjs$2 = {}; + +var cjs$1; +var hasRequiredCjs$2; + +function requireCjs$2() { + if (hasRequiredCjs$2) return cjs$1; + hasRequiredCjs$2 = 1; + + // http://www.w3.org/TR/CSS21/grammar.html + // https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027 + var COMMENT_REGEX = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g; + + var NEWLINE_REGEX = /\n/g; + var WHITESPACE_REGEX = /^\s*/; + + // declaration + var PROPERTY_REGEX = /^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/; + var COLON_REGEX = /^:\s*/; + var VALUE_REGEX = /^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/; + var SEMICOLON_REGEX = /^[;\s]*/; + + // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill + var TRIM_REGEX = /^\s+|\s+$/g; + + // strings + var NEWLINE = "\n"; + var FORWARD_SLASH = "/"; + var ASTERISK = "*"; + var EMPTY_STRING = ""; + + // types + var TYPE_COMMENT = "comment"; + var TYPE_DECLARATION = "declaration"; + + /** + * @param {String} style + * @param {Object} [options] + * @return {Object[]} + * @throws {TypeError} + * @throws {Error} + */ + function index(style, options) { + if (typeof style !== "string") { + throw new TypeError("First argument must be a string"); + } + + if (!style) return []; + + options = options || {}; + + /** + * Positional. + */ + var lineno = 1; + var column = 1; + + /** + * Update lineno and column based on `str`. + * + * @param {String} str + */ + function updatePosition(str) { + var lines = str.match(NEWLINE_REGEX); + if (lines) lineno += lines.length; + var i = str.lastIndexOf(NEWLINE); + column = ~i ? str.length - i : column + str.length; + } + + /** + * Mark position and patch `node.position`. + * + * @return {Function} + */ + function position() { + var start = { line: lineno, column: column }; + return function (node) { + node.position = new Position(start); + whitespace(); + return node; + }; + } + + /** + * Store position information for a node. + * + * @constructor + * @property {Object} start + * @property {Object} end + * @property {undefined|String} source + */ + function Position(start) { + this.start = start; + this.end = { line: lineno, column: column }; + this.source = options.source; + } + + /** + * Non-enumerable source string. + */ + Position.prototype.content = style; + + /** + * Error `msg`. + * + * @param {String} msg + * @throws {Error} + */ + function error(msg) { + var err = new Error(options.source + ":" + lineno + ":" + column + ": " + msg); + err.reason = msg; + err.filename = options.source; + err.line = lineno; + err.column = column; + err.source = style; + + if (options.silent); + else { + throw err; + } + } + + /** + * Match `re` and return captures. + * + * @param {RegExp} re + * @return {undefined|Array} + */ + function match(re) { + var m = re.exec(style); + if (!m) return; + var str = m[0]; + updatePosition(str); + style = style.slice(str.length); + return m; + } + + /** + * Parse whitespace. + */ + function whitespace() { + match(WHITESPACE_REGEX); + } + + /** + * Parse comments. + * + * @param {Object[]} [rules] + * @return {Object[]} + */ + function comments(rules) { + var c; + rules = rules || []; + while ((c = comment())) { + if (c !== false) { + rules.push(c); + } + } + return rules; + } + + /** + * Parse comment. + * + * @return {Object} + * @throws {Error} + */ + function comment() { + var pos = position(); + if (FORWARD_SLASH != style.charAt(0) || ASTERISK != style.charAt(1)) return; + + var i = 2; + while (EMPTY_STRING != style.charAt(i) && (ASTERISK != style.charAt(i) || FORWARD_SLASH != style.charAt(i + 1))) { + ++i; + } + i += 2; + + if (EMPTY_STRING === style.charAt(i - 1)) { + return error("End of comment missing"); + } + + var str = style.slice(2, i - 2); + column += 2; + updatePosition(str); + style = style.slice(i); + column += 2; + + return pos({ + type: TYPE_COMMENT, + comment: str, + }); + } + + /** + * Parse declaration. + * + * @return {Object} + * @throws {Error} + */ + function declaration() { + var pos = position(); + + // prop + var prop = match(PROPERTY_REGEX); + if (!prop) return; + comment(); + + // : + if (!match(COLON_REGEX)) return error("property missing ':'"); + + // val + var val = match(VALUE_REGEX); + + var ret = pos({ + type: TYPE_DECLARATION, + property: trim(prop[0].replace(COMMENT_REGEX, EMPTY_STRING)), + value: val ? trim(val[0].replace(COMMENT_REGEX, EMPTY_STRING)) : EMPTY_STRING, + }); + + // ; + match(SEMICOLON_REGEX); + + return ret; + } + + /** + * Parse declarations. + * + * @return {Object[]} + */ + function declarations() { + var decls = []; + + comments(decls); + + // declarations + var decl; + while ((decl = declaration())) { + if (decl !== false) { + decls.push(decl); + comments(decls); + } + } + + return decls; + } + + whitespace(); + return declarations(); + } + + /** + * Trim `str`. + * + * @param {String} str + * @return {String} + */ + function trim(str) { + return str ? str.replace(TRIM_REGEX, EMPTY_STRING) : EMPTY_STRING; + } + + cjs$1 = index; + + return cjs$1; +} + +var hasRequiredCjs$1; + +function requireCjs$1() { + if (hasRequiredCjs$1) return cjs$2; + hasRequiredCjs$1 = 1; + var __importDefault = + (cjs$2 && cjs$2.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; + Object.defineProperty(cjs$2, "__esModule", { value: true }); + cjs$2.default = StyleToObject; + const inline_style_parser_1 = __importDefault(requireCjs$2()); + /** + * Parses inline style to object. + * + * @param style - Inline style. + * @param iterator - Iterator. + * @returns - Style object or null. + * + * @example Parsing inline style to object: + * + * ```js + * import parse from 'style-to-object'; + * parse('line-height: 42;'); // { 'line-height': '42' } + * ``` + */ + function StyleToObject(style, iterator) { + let styleObject = null; + if (!style || typeof style !== "string") { + return styleObject; + } + const declarations = (0, inline_style_parser_1.default)(style); + const hasIterator = typeof iterator === "function"; + declarations.forEach((declaration) => { + if (declaration.type !== "declaration") { + return; + } + const { property, value } = declaration; + if (hasIterator) { + iterator(property, value, declaration); + } else if (value) { + styleObject = styleObject || {}; + styleObject[property] = value; + } + }); + return styleObject; + } + + return cjs$2; +} + +var utilities = {}; + +var hasRequiredUtilities; + +function requireUtilities() { + if (hasRequiredUtilities) return utilities; + hasRequiredUtilities = 1; + Object.defineProperty(utilities, "__esModule", { value: true }); + utilities.camelCase = void 0; + var CUSTOM_PROPERTY_REGEX = /^--[a-zA-Z0-9_-]+$/; + var HYPHEN_REGEX = /-([a-z])/g; + var NO_HYPHEN_REGEX = /^[^-]+$/; + var VENDOR_PREFIX_REGEX = /^-(webkit|moz|ms|o|khtml)-/; + var MS_VENDOR_PREFIX_REGEX = /^-(ms)-/; + /** + * Checks whether to skip camelCase. + */ + var skipCamelCase = function (property) { + return !property || NO_HYPHEN_REGEX.test(property) || CUSTOM_PROPERTY_REGEX.test(property); + }; + /** + * Replacer that capitalizes first character. + */ + var capitalize = function (match, character) { + return character.toUpperCase(); + }; + /** + * Replacer that removes beginning hyphen of vendor prefix property. + */ + var trimHyphen = function (match, prefix) { + return "".concat(prefix, "-"); + }; + /** + * CamelCases a CSS property. + */ + var camelCase = function (property, options) { + if (options === void 0) { + options = {}; + } + if (skipCamelCase(property)) { + return property; + } + property = property.toLowerCase(); + if (options.reactCompat) { + // `-ms` vendor prefix should not be capitalized + property = property.replace(MS_VENDOR_PREFIX_REGEX, trimHyphen); + } else { + // for non-React, remove first hyphen so vendor prefix is not capitalized + property = property.replace(VENDOR_PREFIX_REGEX, trimHyphen); + } + return property.replace(HYPHEN_REGEX, capitalize); + }; + utilities.camelCase = camelCase; + + return utilities; +} + +var cjs; +var hasRequiredCjs; + +function requireCjs() { + if (hasRequiredCjs) return cjs; + hasRequiredCjs = 1; + var __importDefault = + (cjs && cjs.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; + var style_to_object_1 = __importDefault(requireCjs$1()); + var utilities_1 = requireUtilities(); + /** + * Parses CSS inline style to JavaScript object (camelCased). + */ + function StyleToJS(style, options) { + var output = {}; + if (!style || typeof style !== "string") { + return output; + } + (0, style_to_object_1.default)(style, function (property, value) { + // skip CSS comment + if (property && value) { + output[(0, utilities_1.camelCase)(property, options)] = value; + } + }); + return output; + } + StyleToJS.default = StyleToJS; + cjs = StyleToJS; + + return cjs; +} + +var cjsExports = requireCjs(); +const styleToJs = /*@__PURE__*/ getDefaultExportFromCjs(cjsExports); + +/** + * @import {Identifier, Literal, MemberExpression} from 'estree' + * @import {Jsx, JsxDev, Options, Props} from 'hast-util-to-jsx-runtime' + * @import {Element, Nodes, Parents, Root, Text} from 'hast' + * @import {MdxFlowExpressionHast, MdxTextExpressionHast} from 'mdast-util-mdx-expression' + * @import {MdxJsxFlowElementHast, MdxJsxTextElementHast} from 'mdast-util-mdx-jsx' + * @import {MdxjsEsmHast} from 'mdast-util-mdxjs-esm' + * @import {Position} from 'unist' + * @import {Child, Create, Field, JsxElement, State, Style} from './types.js' + */ + +// To do: next major: `Object.hasOwn`. +const own = {}.hasOwnProperty; + +/** @type {Map} */ +const emptyMap = new Map(); + +const cap = /[A-Z]/g; + +// `react-dom` triggers a warning for *any* white space in tables. +// To follow GFM, `mdast-util-to-hast` injects line endings between elements. +// Other tools might do so too, but they don’t do here, so we remove all of +// that. + +// See: . +// See: . +// See: . +// See: . +// See: . +// See: . +const tableElements = new Set(["table", "tbody", "thead", "tfoot", "tr"]); + +const tableCellElement = new Set(["td", "th"]); + +const docs = "https://github.com/syntax-tree/hast-util-to-jsx-runtime"; + +/** + * Transform a hast tree to preact, react, solid, svelte, vue, etc., + * with an automatic JSX runtime. + * + * @param {Nodes} tree + * Tree to transform. + * @param {Options} options + * Configuration (required). + * @returns {JsxElement} + * JSX element. + */ + +function toJsxRuntime(tree, options) { + if (!options || options.Fragment === undefined) { + throw new TypeError("Expected `Fragment` in options"); + } + + const filePath = options.filePath || undefined; + /** @type {Create} */ + let create; + + if (options.development) { + if (typeof options.jsxDEV !== "function") { + throw new TypeError("Expected `jsxDEV` in options when `development: true`"); + } + + create = developmentCreate(filePath, options.jsxDEV); + } else { + if (typeof options.jsx !== "function") { + throw new TypeError("Expected `jsx` in production options"); + } + + if (typeof options.jsxs !== "function") { + throw new TypeError("Expected `jsxs` in production options"); + } + + create = productionCreate(filePath, options.jsx, options.jsxs); + } + + /** @type {State} */ + const state = { + Fragment: options.Fragment, + ancestors: [], + components: options.components || {}, + create, + elementAttributeNameCase: options.elementAttributeNameCase || "react", + evaluater: options.createEvaluater ? options.createEvaluater() : undefined, + filePath, + ignoreInvalidStyle: options.ignoreInvalidStyle || false, + passKeys: options.passKeys !== false, + passNode: options.passNode || false, + schema: options.space === "svg" ? svg : html, + stylePropertyNameCase: options.stylePropertyNameCase || "dom", + tableCellAlignToStyle: options.tableCellAlignToStyle !== false, + }; + + const result = one(state, tree, undefined); + + // JSX element. + if (result && typeof result !== "string") { + return result; + } + + // Text node or something that turned into nothing. + return state.create(tree, state.Fragment, { children: result || undefined }, undefined); +} + +/** + * Transform a node. + * + * @param {State} state + * Info passed around. + * @param {Nodes} node + * Current node. + * @param {string | undefined} key + * Key. + * @returns {Child | undefined} + * Child, optional. + */ +function one(state, node, key) { + if (node.type === "element") { + return element(state, node, key); + } + + if (node.type === "mdxFlowExpression" || node.type === "mdxTextExpression") { + return mdxExpression(state, node); + } + + if (node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") { + return mdxJsxElement(state, node, key); + } + + if (node.type === "mdxjsEsm") { + return mdxEsm(state, node); + } + + if (node.type === "root") { + return root(state, node, key); + } + + if (node.type === "text") { + return text$1(state, node); + } +} + +/** + * Handle element. + * + * @param {State} state + * Info passed around. + * @param {Element} node + * Current node. + * @param {string | undefined} key + * Key. + * @returns {Child | undefined} + * Child, optional. + */ +function element(state, node, key) { + const parentSchema = state.schema; + let schema = parentSchema; + + if (node.tagName.toLowerCase() === "svg" && parentSchema.space === "html") { + schema = svg; + state.schema = schema; + } + + state.ancestors.push(node); + + const type = findComponentFromName(state, node.tagName, false); + const props = createElementProps(state, node); + let children = createChildren(state, node); + + if (tableElements.has(node.tagName)) { + children = children.filter(function (child) { + return typeof child === "string" ? !whitespace(child) : true; + }); + } + + addNode(state, props, type, node); + addChildren(props, children); + + // Restore. + state.ancestors.pop(); + state.schema = parentSchema; + + return state.create(node, type, props, key); +} + +/** + * Handle MDX expression. + * + * @param {State} state + * Info passed around. + * @param {MdxFlowExpressionHast | MdxTextExpressionHast} node + * Current node. + * @returns {Child | undefined} + * Child, optional. + */ +function mdxExpression(state, node) { + if (node.data && node.data.estree && state.evaluater) { + const program = node.data.estree; + const expression = program.body[0]; + ok(expression.type === "ExpressionStatement"); + + // Assume result is a child. + return /** @type {Child | undefined} */ (state.evaluater.evaluateExpression(expression.expression)); + } + + crashEstree(state, node.position); +} + +/** + * Handle MDX ESM. + * + * @param {State} state + * Info passed around. + * @param {MdxjsEsmHast} node + * Current node. + * @returns {Child | undefined} + * Child, optional. + */ +function mdxEsm(state, node) { + if (node.data && node.data.estree && state.evaluater) { + // Assume result is a child. + return /** @type {Child | undefined} */ (state.evaluater.evaluateProgram(node.data.estree)); + } + + crashEstree(state, node.position); +} + +/** + * Handle MDX JSX. + * + * @param {State} state + * Info passed around. + * @param {MdxJsxFlowElementHast | MdxJsxTextElementHast} node + * Current node. + * @param {string | undefined} key + * Key. + * @returns {Child | undefined} + * Child, optional. + */ +function mdxJsxElement(state, node, key) { + const parentSchema = state.schema; + let schema = parentSchema; + + if (node.name === "svg" && parentSchema.space === "html") { + schema = svg; + state.schema = schema; + } + + state.ancestors.push(node); + + const type = node.name === null ? state.Fragment : findComponentFromName(state, node.name, true); + const props = createJsxElementProps(state, node); + const children = createChildren(state, node); + + addNode(state, props, type, node); + addChildren(props, children); + + // Restore. + state.ancestors.pop(); + state.schema = parentSchema; + + return state.create(node, type, props, key); +} + +/** + * Handle root. + * + * @param {State} state + * Info passed around. + * @param {Root} node + * Current node. + * @param {string | undefined} key + * Key. + * @returns {Child | undefined} + * Child, optional. + */ +function root(state, node, key) { + /** @type {Props} */ + const props = {}; + + addChildren(props, createChildren(state, node)); + + return state.create(node, state.Fragment, props, key); +} + +/** + * Handle text. + * + * @param {State} _ + * Info passed around. + * @param {Text} node + * Current node. + * @returns {Child | undefined} + * Child, optional. + */ +function text$1(_, node) { + return node.value; +} + +/** + * Add `node` to props. + * + * @param {State} state + * Info passed around. + * @param {Props} props + * Props. + * @param {unknown} type + * Type. + * @param {Element | MdxJsxFlowElementHast | MdxJsxTextElementHast} node + * Node. + * @returns {undefined} + * Nothing. + */ +function addNode(state, props, type, node) { + // If this is swapped out for a component: + if (typeof type !== "string" && type !== state.Fragment && state.passNode) { + props.node = node; + } +} + +/** + * Add children to props. + * + * @param {Props} props + * Props. + * @param {Array} children + * Children. + * @returns {undefined} + * Nothing. + */ +function addChildren(props, children) { + if (children.length > 0) { + const value = children.length > 1 ? children : children[0]; + + if (value) { + props.children = value; + } + } +} + +/** + * @param {string | undefined} _ + * Path to file. + * @param {Jsx} jsx + * Dynamic. + * @param {Jsx} jsxs + * Static. + * @returns {Create} + * Create a production element. + */ +function productionCreate(_, jsx, jsxs) { + return create; + /** @type {Create} */ + function create(_, type, props, key) { + // Only an array when there are 2 or more children. + const isStaticChildren = Array.isArray(props.children); + const fn = isStaticChildren ? jsxs : jsx; + return key ? fn(type, props, key) : fn(type, props); + } +} + +/** + * @param {string | undefined} filePath + * Path to file. + * @param {JsxDev} jsxDEV + * Development. + * @returns {Create} + * Create a development element. + */ +function developmentCreate(filePath, jsxDEV) { + return create; + /** @type {Create} */ + function create(node, type, props, key) { + // Only an array when there are 2 or more children. + const isStaticChildren = Array.isArray(props.children); + const point = pointStart(node); + return jsxDEV( + type, + props, + key, + isStaticChildren, + { + columnNumber: point ? point.column - 1 : undefined, + fileName: filePath, + lineNumber: point ? point.line : undefined, + }, + undefined, + ); + } +} + +/** + * Create props from an element. + * + * @param {State} state + * Info passed around. + * @param {Element} node + * Current element. + * @returns {Props} + * Props. + */ +function createElementProps(state, node) { + /** @type {Props} */ + const props = {}; + /** @type {string | undefined} */ + let alignValue; + /** @type {string} */ + let prop; + + for (prop in node.properties) { + if (prop !== "children" && own.call(node.properties, prop)) { + const result = createProperty(state, prop, node.properties[prop]); + + if (result) { + const [key, value] = result; + + if (state.tableCellAlignToStyle && key === "align" && typeof value === "string" && tableCellElement.has(node.tagName)) { + alignValue = value; + } else { + props[key] = value; + } + } + } + } + + if (alignValue) { + // Assume style is an object. + const style = /** @type {Style} */ (props.style || (props.style = {})); + style[state.stylePropertyNameCase === "css" ? "text-align" : "textAlign"] = alignValue; + } + + return props; +} + +/** + * Create props from a JSX element. + * + * @param {State} state + * Info passed around. + * @param {MdxJsxFlowElementHast | MdxJsxTextElementHast} node + * Current JSX element. + * @returns {Props} + * Props. + */ +function createJsxElementProps(state, node) { + /** @type {Props} */ + const props = {}; + + for (const attribute of node.attributes) { + if (attribute.type === "mdxJsxExpressionAttribute") { + if (attribute.data && attribute.data.estree && state.evaluater) { + const program = attribute.data.estree; + const expression = program.body[0]; + ok(expression.type === "ExpressionStatement"); + const objectExpression = expression.expression; + ok(objectExpression.type === "ObjectExpression"); + const property = objectExpression.properties[0]; + ok(property.type === "SpreadElement"); + + Object.assign(props, state.evaluater.evaluateExpression(property.argument)); + } else { + crashEstree(state, node.position); + } + } else { + // For JSX, the author is responsible of passing in the correct values. + const name = attribute.name; + /** @type {unknown} */ + let value; + + if (attribute.value && typeof attribute.value === "object") { + if (attribute.value.data && attribute.value.data.estree && state.evaluater) { + const program = attribute.value.data.estree; + const expression = program.body[0]; + ok(expression.type === "ExpressionStatement"); + value = state.evaluater.evaluateExpression(expression.expression); + } else { + crashEstree(state, node.position); + } + } else { + value = attribute.value === null ? true : attribute.value; + } + + // Assume a prop. + props[name] = /** @type {Props[keyof Props]} */ (value); + } + } + + return props; +} + +/** + * Create children. + * + * @param {State} state + * Info passed around. + * @param {Parents} node + * Current element. + * @returns {Array} + * Children. + */ +function createChildren(state, node) { + /** @type {Array} */ + const children = []; + let index = -1; + /** @type {Map} */ + // Note: test this when Solid doesn’t want to merge my upcoming PR. + /* c8 ignore next */ + const countsByName = state.passKeys ? new Map() : emptyMap; + + while (++index < node.children.length) { + const child = node.children[index]; + /** @type {string | undefined} */ + let key; + + if (state.passKeys) { + const name = + child.type === "element" ? child.tagName + : child.type === "mdxJsxFlowElement" || child.type === "mdxJsxTextElement" ? child.name + : undefined; + + if (name) { + const count = countsByName.get(name) || 0; + key = name + "-" + count; + countsByName.set(name, count + 1); + } + } + + const result = one(state, child, key); + if (result !== undefined) children.push(result); + } + + return children; +} + +/** + * Handle a property. + * + * @param {State} state + * Info passed around. + * @param {string} prop + * Key. + * @param {Array | boolean | number | string | null | undefined} value + * hast property value. + * @returns {Field | undefined} + * Field for runtime, optional. + */ +function createProperty(state, prop, value) { + const info = find(state.schema, prop); + + // Ignore nullish and `NaN` values. + if (value === null || value === undefined || (typeof value === "number" && Number.isNaN(value))) { + return; + } + + if (Array.isArray(value)) { + // Accept `array`. + // Most props are space-separated. + value = info.commaSeparated ? stringify(value) : stringify$1(value); + } + + // React only accepts `style` as object. + if (info.property === "style") { + let styleObject = typeof value === "object" ? value : parseStyle(state, String(value)); + + if (state.stylePropertyNameCase === "css") { + styleObject = transformStylesToCssCasing(styleObject); + } + + return ["style", styleObject]; + } + + return [state.elementAttributeNameCase === "react" && info.space ? hastToReact[info.property] || info.property : info.attribute, value]; +} + +/** + * Parse a CSS declaration to an object. + * + * @param {State} state + * Info passed around. + * @param {string} value + * CSS declarations. + * @returns {Style} + * Properties. + * @throws + * Throws `VFileMessage` when CSS cannot be parsed. + */ +function parseStyle(state, value) { + try { + return styleToJs(value, { reactCompat: true }); + } catch (error) { + if (state.ignoreInvalidStyle) { + return {}; + } + + const cause = /** @type {Error} */ (error); + const message = new VFileMessage("Cannot parse `style` attribute", { + ancestors: state.ancestors, + cause, + ruleId: "style", + source: "hast-util-to-jsx-runtime", + }); + message.file = state.filePath || undefined; + message.url = docs + "#cannot-parse-style-attribute"; + + throw message; + } +} + +/** + * Create a JSX name from a string. + * + * @param {State} state + * To do. + * @param {string} name + * Name. + * @param {boolean} allowExpression + * Allow member expressions and identifiers. + * @returns {unknown} + * To do. + */ +function findComponentFromName(state, name$1, allowExpression) { + /** @type {Identifier | Literal | MemberExpression} */ + let result; + + if (!allowExpression) { + result = { type: "Literal", value: name$1 }; + } else if (name$1.includes(".")) { + const identifiers = name$1.split("."); + let index = -1; + /** @type {Identifier | Literal | MemberExpression | undefined} */ + let node; + + while (++index < identifiers.length) { + /** @type {Identifier | Literal} */ + const prop = name(identifiers[index]) ? { type: "Identifier", name: identifiers[index] } : { type: "Literal", value: identifiers[index] }; + node = + node ? + { + type: "MemberExpression", + object: node, + property: prop, + computed: Boolean(index && prop.type === "Literal"), + optional: false, + } + : prop; + } + result = node; + } else { + result = name(name$1) && !/^[a-z]/.test(name$1) ? { type: "Identifier", name: name$1 } : { type: "Literal", value: name$1 }; + } + + // Only literals can be passed in `components` currently. + // No identifiers / member expressions. + if (result.type === "Literal") { + const name = /** @type {string | number} */ (result.value); + return own.call(state.components, name) ? state.components[name] : name; + } + + // Assume component. + if (state.evaluater) { + return state.evaluater.evaluateExpression(result); + } + + crashEstree(state); +} + +/** + * @param {State} state + * @param {Position | undefined} [place] + * @returns {never} + */ +function crashEstree(state, place) { + const message = new VFileMessage("Cannot handle MDX estrees without `createEvaluater`", { + ancestors: state.ancestors, + place, + ruleId: "mdx-estree", + source: "hast-util-to-jsx-runtime", + }); + message.file = state.filePath || undefined; + message.url = docs + "#cannot-handle-mdx-estrees-without-createevaluater"; + + throw message; +} + +/** + * Transform a DOM casing style object to a CSS casing style object. + * + * @param {Style} domCasing + * @returns {Style} + */ +function transformStylesToCssCasing(domCasing) { + /** @type {Style} */ + const cssCasing = {}; + /** @type {string} */ + let from; + + for (from in domCasing) { + if (own.call(domCasing, from)) { + cssCasing[transformStyleToCssCasing(from)] = domCasing[from]; + } + } + + return cssCasing; +} + +/** + * Transform a DOM casing style field to a CSS casing style field. + * + * @param {string} from + * @returns {string} + */ +function transformStyleToCssCasing(from) { + let to = from.replace(cap, toDash); + // Handle `ms-xxx` -> `-ms-xxx`. + if (to.slice(0, 3) === "ms-") to = "-" + to; + return to; +} + +/** + * Make `$0` dash cased. + * + * @param {string} $0 + * Capitalized ASCII leter. + * @returns {string} + * Dash and lower letter. + */ +function toDash($0) { + return "-" + $0.toLowerCase(); +} + +/** + * HTML URL properties. + * + * Each key is a property name and each value is a list of tag names it applies + * to or `null` if it applies to all elements. + * + * @type {Record | null>} + */ +const urlAttributes = { + action: ["form"], + cite: ["blockquote", "del", "ins", "q"], + data: ["object"], + formAction: ["button", "input"], + href: ["a", "area", "base", "link"], + icon: ["menuitem"], + itemId: null, + manifest: ["html"], + ping: ["a", "area"], + poster: ["video"], + src: ["audio", "embed", "iframe", "img", "input", "script", "source", "track", "video"], +}; + +const { useEffect: useEffect$2, useState: useState$2 } = await importShared("react"); + +const changelog = "https://github.com/remarkjs/react-markdown/blob/main/changelog.md"; + +/** @type {PluggableList} */ +const emptyPlugins = []; +/** @type {Readonly} */ +const emptyRemarkRehypeOptions = { allowDangerousHtml: true }; +const safeProtocol = /^(https?|ircs?|mailto|xmpp)$/i; + +// Mutable because we `delete` any time it’s used and a message is sent. +/** @type {ReadonlyArray>} */ +const deprecations = [ + { from: "astPlugins", id: "remove-buggy-html-in-markdown-parser" }, + { from: "allowDangerousHtml", id: "remove-buggy-html-in-markdown-parser" }, + { + from: "allowNode", + id: "replace-allownode-allowedtypes-and-disallowedtypes", + to: "allowElement", + }, + { + from: "allowedTypes", + id: "replace-allownode-allowedtypes-and-disallowedtypes", + to: "allowedElements", + }, + { from: "className", id: "remove-classname" }, + { + from: "disallowedTypes", + id: "replace-allownode-allowedtypes-and-disallowedtypes", + to: "disallowedElements", + }, + { from: "escapeHtml", id: "remove-buggy-html-in-markdown-parser" }, + { from: "includeElementIndex", id: "#remove-includeelementindex" }, + { + from: "includeNodeIndex", + id: "change-includenodeindex-to-includeelementindex", + }, + { from: "linkTarget", id: "remove-linktarget" }, + { from: "plugins", id: "change-plugins-to-remarkplugins", to: "remarkPlugins" }, + { from: "rawSourcePos", id: "#remove-rawsourcepos" }, + { from: "renderers", id: "change-renderers-to-components", to: "components" }, + { from: "source", id: "change-source-to-children", to: "children" }, + { from: "sourcePos", id: "#remove-sourcepos" }, + { from: "transformImageUri", id: "#add-urltransform", to: "urlTransform" }, + { from: "transformLinkUri", id: "#add-urltransform", to: "urlTransform" }, +]; + +/** + * Component to render markdown. + * + * This is a synchronous component. + * When using async plugins, + * see {@linkcode MarkdownAsync} or {@linkcode MarkdownHooks}. + * + * @param {Readonly} options + * Props. + * @returns {ReactElement} + * React element. + */ +function Markdown(options) { + const processor = createProcessor(options); + const file = createFile(options); + return post(processor.runSync(processor.parse(file), file), options); +} + +/** + * Set up the `unified` processor. + * + * @param {Readonly} options + * Props. + * @returns {Processor} + * Result. + */ +function createProcessor(options) { + const rehypePlugins = options.rehypePlugins || emptyPlugins; + const remarkPlugins = options.remarkPlugins || emptyPlugins; + const remarkRehypeOptions = options.remarkRehypeOptions ? { ...options.remarkRehypeOptions, ...emptyRemarkRehypeOptions } : emptyRemarkRehypeOptions; + + const processor = unified().use(remarkParse).use(remarkPlugins).use(remarkRehype, remarkRehypeOptions).use(rehypePlugins); + + return processor; +} + +/** + * Set up the virtual file. + * + * @param {Readonly} options + * Props. + * @returns {VFile} + * Result. + */ +function createFile(options) { + const children = options.children || ""; + const file = new VFile(); + + if (typeof children === "string") { + file.value = children; + } + + return file; +} + +/** + * Process the result from unified some more. + * + * @param {Nodes} tree + * Tree. + * @param {Readonly} options + * Props. + * @returns {ReactElement} + * React element. + */ +function post(tree, options) { + const allowedElements = options.allowedElements; + const allowElement = options.allowElement; + const components = options.components; + const disallowedElements = options.disallowedElements; + const skipHtml = options.skipHtml; + const unwrapDisallowed = options.unwrapDisallowed; + const urlTransform = options.urlTransform || defaultUrlTransform; + + for (const deprecation of deprecations) { + if (Object.hasOwn(options, deprecation.from)) { + unreachable( + "Unexpected `" + + deprecation.from + + "` prop, " + + (deprecation.to ? "use `" + deprecation.to + "` instead" : "remove it") + + " (see <" + + changelog + + "#" + + deprecation.id + + "> for more info)", + ); + } + } + + visit(tree, transform); + + return toJsxRuntime(tree, { + Fragment: jsxRuntimeExports.Fragment, + components, + ignoreInvalidStyle: true, + jsx: jsxRuntimeExports.jsx, + jsxs: jsxRuntimeExports.jsxs, + passKeys: true, + passNode: true, + }); + + /** @type {BuildVisitor} */ + function transform(node, index, parent) { + if (node.type === "raw" && parent && typeof index === "number") { + if (skipHtml) { + parent.children.splice(index, 1); + } else { + parent.children[index] = { type: "text", value: node.value }; + } + + return index; + } + + if (node.type === "element") { + /** @type {string} */ + let key; + + for (key in urlAttributes) { + if (Object.hasOwn(urlAttributes, key) && Object.hasOwn(node.properties, key)) { + const value = node.properties[key]; + const test = urlAttributes[key]; + if (test === null || test.includes(node.tagName)) { + node.properties[key] = urlTransform(String(value || ""), key, node); + } + } + } + } + + if (node.type === "element") { + let remove = + allowedElements ? !allowedElements.includes(node.tagName) + : disallowedElements ? disallowedElements.includes(node.tagName) + : false; + + if (!remove && allowElement && typeof index === "number") { + remove = !allowElement(node, index, parent); + } + + if (remove && parent && typeof index === "number") { + if (unwrapDisallowed && node.children) { + parent.children.splice(index, 1, ...node.children); + } else { + parent.children.splice(index, 1); + } + + return index; + } + } + } +} + +/** + * Make a URL safe. + * + * @satisfies {UrlTransform} + * @param {string} value + * URL. + * @returns {string} + * Safe URL. + */ +function defaultUrlTransform(value) { + // Same as: + // + // But without the `encode` part. + const colon = value.indexOf(":"); + const questionMark = value.indexOf("?"); + const numberSign = value.indexOf("#"); + const slash = value.indexOf("/"); + + if ( + // If there is no protocol, it’s relative. + colon === -1 || + // If the first colon is after a `?`, `#`, or `/`, it’s not a protocol. + (slash !== -1 && colon > slash) || + (questionMark !== -1 && colon > questionMark) || + (numberSign !== -1 && colon > numberSign) || + // It is a protocol, it should be allowed. + safeProtocol.test(value.slice(0, colon)) + ) { + return value; + } + + return ""; +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Determine whether the given `arg` is a message. + * If `desc` is set, determine whether `arg` is this specific message. + */ +function isMessage(arg, schema) { + const isMessage = arg !== null && typeof arg == "object" && "$typeName" in arg && typeof arg.$typeName == "string"; + if (!isMessage) { + return false; + } + if (schema === undefined) { + return true; + } + return schema.typeName === arg.$typeName; +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Scalar value types. This is a subset of field types declared by protobuf + * enum google.protobuf.FieldDescriptorProto.Type The types GROUP and MESSAGE + * are omitted, but the numerical values are identical. + */ +var ScalarType; +(function (ScalarType) { + // 0 is reserved for errors. + // Order is weird for historical reasons. + ScalarType[(ScalarType["DOUBLE"] = 1)] = "DOUBLE"; + ScalarType[(ScalarType["FLOAT"] = 2)] = "FLOAT"; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + // negative values are likely. + ScalarType[(ScalarType["INT64"] = 3)] = "INT64"; + ScalarType[(ScalarType["UINT64"] = 4)] = "UINT64"; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + // negative values are likely. + ScalarType[(ScalarType["INT32"] = 5)] = "INT32"; + ScalarType[(ScalarType["FIXED64"] = 6)] = "FIXED64"; + ScalarType[(ScalarType["FIXED32"] = 7)] = "FIXED32"; + ScalarType[(ScalarType["BOOL"] = 8)] = "BOOL"; + ScalarType[(ScalarType["STRING"] = 9)] = "STRING"; + // Tag-delimited aggregate. + // Group type is deprecated and not supported in proto3. However, Proto3 + // implementations should still be able to parse the group wire format and + // treat group fields as unknown fields. + // TYPE_GROUP = 10, + // TYPE_MESSAGE = 11, // Length-delimited aggregate. + // New in version 2. + ScalarType[(ScalarType["BYTES"] = 12)] = "BYTES"; + ScalarType[(ScalarType["UINT32"] = 13)] = "UINT32"; + // TYPE_ENUM = 14, + ScalarType[(ScalarType["SFIXED32"] = 15)] = "SFIXED32"; + ScalarType[(ScalarType["SFIXED64"] = 16)] = "SFIXED64"; + ScalarType[(ScalarType["SINT32"] = 17)] = "SINT32"; + ScalarType[(ScalarType["SINT64"] = 18)] = "SINT64"; +})(ScalarType || (ScalarType = {})); + +// Copyright 2008 Google Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Code generated by the Protocol Buffer compiler is owned by the owner +// of the input file used when generating it. This code is not +// standalone and requires a support library to be linked with it. This +// support library is itself covered by the above license. +/** + * Read a 64 bit varint as two JS numbers. + * + * Returns tuple: + * [0]: low bits + * [1]: high bits + * + * Copyright 2008 Google Inc. All rights reserved. + * + * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/buffer_decoder.js#L175 + */ +function varint64read() { + let lowBits = 0; + let highBits = 0; + for (let shift = 0; shift < 28; shift += 7) { + let b = this.buf[this.pos++]; + lowBits |= (b & 0x7f) << shift; + if ((b & 0x80) == 0) { + this.assertBounds(); + return [lowBits, highBits]; + } + } + let middleByte = this.buf[this.pos++]; + // last four bits of the first 32 bit number + lowBits |= (middleByte & 0x0f) << 28; + // 3 upper bits are part of the next 32 bit number + highBits = (middleByte & 0x70) >> 4; + if ((middleByte & 0x80) == 0) { + this.assertBounds(); + return [lowBits, highBits]; + } + for (let shift = 3; shift <= 31; shift += 7) { + let b = this.buf[this.pos++]; + highBits |= (b & 0x7f) << shift; + if ((b & 0x80) == 0) { + this.assertBounds(); + return [lowBits, highBits]; + } + } + throw new Error("invalid varint"); +} +// constants for binary math +const TWO_PWR_32_DBL = 0x100000000; +/** + * Parse decimal string of 64 bit integer value as two JS numbers. + * + * Copyright 2008 Google Inc. All rights reserved. + * + * See https://github.com/protocolbuffers/protobuf-javascript/blob/a428c58273abad07c66071d9753bc4d1289de426/experimental/runtime/int64.js#L10 + */ +function int64FromString(dec) { + // Check for minus sign. + const minus = dec[0] === "-"; + if (minus) { + dec = dec.slice(1); + } + // Work 6 decimal digits at a time, acting like we're converting base 1e6 + // digits to binary. This is safe to do with floating point math because + // Number.isSafeInteger(ALL_32_BITS * 1e6) == true. + const base = 1e6; + let lowBits = 0; + let highBits = 0; + function add1e6digit(begin, end) { + // Note: Number('') is 0. + const digit1e6 = Number(dec.slice(begin, end)); + highBits *= base; + lowBits = lowBits * base + digit1e6; + // Carry bits from lowBits to + if (lowBits >= TWO_PWR_32_DBL) { + highBits = highBits + ((lowBits / TWO_PWR_32_DBL) | 0); + lowBits = lowBits % TWO_PWR_32_DBL; + } + } + add1e6digit(-24, -18); + add1e6digit(-18, -12); + add1e6digit(-12, -6); + add1e6digit(-6); + return minus ? negate(lowBits, highBits) : newBits(lowBits, highBits); +} +/** + * Losslessly converts a 64-bit signed integer in 32:32 split representation + * into a decimal string. + * + * Copyright 2008 Google Inc. All rights reserved. + * + * See https://github.com/protocolbuffers/protobuf-javascript/blob/a428c58273abad07c66071d9753bc4d1289de426/experimental/runtime/int64.js#L10 + */ +function int64ToString(lo, hi) { + let bits = newBits(lo, hi); + // If we're treating the input as a signed value and the high bit is set, do + // a manual two's complement conversion before the decimal conversion. + const negative = bits.hi & 0x80000000; + if (negative) { + bits = negate(bits.lo, bits.hi); + } + const result = uInt64ToString(bits.lo, bits.hi); + return negative ? "-" + result : result; +} +/** + * Losslessly converts a 64-bit unsigned integer in 32:32 split representation + * into a decimal string. + * + * Copyright 2008 Google Inc. All rights reserved. + * + * See https://github.com/protocolbuffers/protobuf-javascript/blob/a428c58273abad07c66071d9753bc4d1289de426/experimental/runtime/int64.js#L10 + */ +function uInt64ToString(lo, hi) { + ({ lo, hi } = toUnsigned(lo, hi)); + // Skip the expensive conversion if the number is small enough to use the + // built-in conversions. + // Number.MAX_SAFE_INTEGER = 0x001FFFFF FFFFFFFF, thus any number with + // highBits <= 0x1FFFFF can be safely expressed with a double and retain + // integer precision. + // Proven by: Number.isSafeInteger(0x1FFFFF * 2**32 + 0xFFFFFFFF) == true. + if (hi <= 0x1fffff) { + return String(TWO_PWR_32_DBL * hi + lo); + } + // What this code is doing is essentially converting the input number from + // base-2 to base-1e7, which allows us to represent the 64-bit range with + // only 3 (very large) digits. Those digits are then trivial to convert to + // a base-10 string. + // The magic numbers used here are - + // 2^24 = 16777216 = (1,6777216) in base-1e7. + // 2^48 = 281474976710656 = (2,8147497,6710656) in base-1e7. + // Split 32:32 representation into 16:24:24 representation so our + // intermediate digits don't overflow. + const low = lo & 0xffffff; + const mid = ((lo >>> 24) | (hi << 8)) & 0xffffff; + const high = (hi >> 16) & 0xffff; + // Assemble our three base-1e7 digits, ignoring carries. The maximum + // value in a digit at this step is representable as a 48-bit integer, which + // can be stored in a 64-bit floating point number. + let digitA = low + mid * 6777216 + high * 6710656; + let digitB = mid + high * 8147497; + let digitC = high * 2; + // Apply carries from A to B and from B to C. + const base = 10000000; + if (digitA >= base) { + digitB += Math.floor(digitA / base); + digitA %= base; + } + if (digitB >= base) { + digitC += Math.floor(digitB / base); + digitB %= base; + } + // If digitC is 0, then we should have returned in the trivial code path + // at the top for non-safe integers. Given this, we can assume both digitB + // and digitA need leading zeros. + return digitC.toString() + decimalFrom1e7WithLeadingZeros(digitB) + decimalFrom1e7WithLeadingZeros(digitA); +} +function toUnsigned(lo, hi) { + return { lo: lo >>> 0, hi: hi >>> 0 }; +} +function newBits(lo, hi) { + return { lo: lo | 0, hi: hi | 0 }; +} +/** + * Returns two's compliment negation of input. + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Signed_32-bit_integers + */ +function negate(lowBits, highBits) { + highBits = ~highBits; + if (lowBits) { + lowBits = ~lowBits + 1; + } else { + // If lowBits is 0, then bitwise-not is 0xFFFFFFFF, + // adding 1 to that, results in 0x100000000, which leaves + // the low bits 0x0 and simply adds one to the high bits. + highBits += 1; + } + return newBits(lowBits, highBits); +} +/** + * Returns decimal representation of digit1e7 with leading zeros. + */ +const decimalFrom1e7WithLeadingZeros = (digit1e7) => { + const partial = String(digit1e7); + return "0000000".slice(partial.length) + partial; +}; +/** + * Write a 32 bit varint, signed or unsigned. Same as `varint64write(0, value, bytes)` + * + * Copyright 2008 Google Inc. All rights reserved. + * + * See https://github.com/protocolbuffers/protobuf/blob/1b18833f4f2a2f681f4e4a25cdf3b0a43115ec26/js/binary/encoder.js#L144 + */ +function varint32write(value, bytes) { + if (value >= 0) { + // write value as varint 32 + while (value > 0x7f) { + bytes.push((value & 0x7f) | 0x80); + value = value >>> 7; + } + bytes.push(value); + } else { + for (let i = 0; i < 9; i++) { + bytes.push((value & 127) | 128); + value = value >> 7; + } + bytes.push(1); + } +} +/** + * Read an unsigned 32 bit varint. + * + * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/buffer_decoder.js#L220 + */ +function varint32read() { + let b = this.buf[this.pos++]; + let result = b & 0x7f; + if ((b & 0x80) == 0) { + this.assertBounds(); + return result; + } + b = this.buf[this.pos++]; + result |= (b & 0x7f) << 7; + if ((b & 0x80) == 0) { + this.assertBounds(); + return result; + } + b = this.buf[this.pos++]; + result |= (b & 0x7f) << 14; + if ((b & 0x80) == 0) { + this.assertBounds(); + return result; + } + b = this.buf[this.pos++]; + result |= (b & 0x7f) << 21; + if ((b & 0x80) == 0) { + this.assertBounds(); + return result; + } + // Extract only last 4 bits + b = this.buf[this.pos++]; + result |= (b & 0x0f) << 28; + for (let readBytes = 5; (b & 0x80) !== 0 && readBytes < 10; readBytes++) b = this.buf[this.pos++]; + if ((b & 0x80) != 0) throw new Error("invalid varint"); + this.assertBounds(); + // Result can have 32 bits, convert it to unsigned + return result >>> 0; +} + +var define_process_env_default = {}; +const protoInt64 = /* @__PURE__ */ makeInt64Support(); +function makeInt64Support() { + const dv = new DataView(new ArrayBuffer(8)); + const ok = + typeof BigInt === "function" && + typeof dv.getBigInt64 === "function" && + typeof dv.getBigUint64 === "function" && + typeof dv.setBigInt64 === "function" && + typeof dv.setBigUint64 === "function" && + (!!globalThis.Deno || !!globalThis.Bun || typeof process != "object" || typeof define_process_env_default != "object" || define_process_env_default.BUF_BIGINT_DISABLE !== "1"); + if (ok) { + const MIN = BigInt("-9223372036854775808"); + const MAX = BigInt("9223372036854775807"); + const UMIN = BigInt("0"); + const UMAX = BigInt("18446744073709551615"); + return { + zero: BigInt(0), + supported: true, + parse(value) { + const bi = typeof value == "bigint" ? value : BigInt(value); + if (bi > MAX || bi < MIN) { + throw new Error(`invalid int64: ${value}`); + } + return bi; + }, + uParse(value) { + const bi = typeof value == "bigint" ? value : BigInt(value); + if (bi > UMAX || bi < UMIN) { + throw new Error(`invalid uint64: ${value}`); + } + return bi; + }, + enc(value) { + dv.setBigInt64(0, this.parse(value), true); + return { + lo: dv.getInt32(0, true), + hi: dv.getInt32(4, true), + }; + }, + uEnc(value) { + dv.setBigInt64(0, this.uParse(value), true); + return { + lo: dv.getInt32(0, true), + hi: dv.getInt32(4, true), + }; + }, + dec(lo, hi) { + dv.setInt32(0, lo, true); + dv.setInt32(4, hi, true); + return dv.getBigInt64(0, true); + }, + uDec(lo, hi) { + dv.setInt32(0, lo, true); + dv.setInt32(4, hi, true); + return dv.getBigUint64(0, true); + }, + }; + } + return { + zero: "0", + supported: false, + parse(value) { + if (typeof value != "string") { + value = value.toString(); + } + assertInt64String(value); + return value; + }, + uParse(value) { + if (typeof value != "string") { + value = value.toString(); + } + assertUInt64String(value); + return value; + }, + enc(value) { + if (typeof value != "string") { + value = value.toString(); + } + assertInt64String(value); + return int64FromString(value); + }, + uEnc(value) { + if (typeof value != "string") { + value = value.toString(); + } + assertUInt64String(value); + return int64FromString(value); + }, + dec(lo, hi) { + return int64ToString(lo, hi); + }, + uDec(lo, hi) { + return uInt64ToString(lo, hi); + }, + }; +} +function assertInt64String(value) { + if (!/^-?[0-9]+$/.test(value)) { + throw new Error("invalid int64: " + value); + } +} +function assertUInt64String(value) { + if (!/^[0-9]+$/.test(value)) { + throw new Error("invalid uint64: " + value); + } +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Returns true if both scalar values are equal. + * + * For float and double, values are compared following IEEE semantics: -0 + * equals 0, and NaN does not equal NaN. This is value equality, not identity. + * + * It deliberately differs from isScalarZeroValue, which treats -0 as distinct + * from 0. A value can equal the zero value without being a zero value + * (scalarEquals(DOUBLE, -0, 0) is true while isScalarZeroValue(DOUBLE, -0) is + * false) so this function must not be used to derive implicit presence. + */ +function scalarEquals(type, a, b) { + if (a === b) { + // This correctly matches equal values except BYTES and (possibly) 64-bit integers. + return true; + } + // Special case BYTES - we need to compare each byte individually + if (type == ScalarType.BYTES) { + if (!(a instanceof Uint8Array) || !(b instanceof Uint8Array)) { + return false; + } + if (a.length !== b.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + return true; + } + // Special case 64-bit integers - we support number, string and bigint representation. + switch (type) { + case ScalarType.UINT64: + case ScalarType.FIXED64: + case ScalarType.INT64: + case ScalarType.SFIXED64: + case ScalarType.SINT64: + // Loose comparison will match between 0n, 0 and "0". + return a == b; + } + // Anything that hasn't been caught by strict comparison or special cased + // BYTES and 64-bit integers is not equal. + return false; +} +/** + * Returns the zero value for the given scalar type, the value a field of this + * type has when unset: 0 for numeric types, "" for strings, false for + * booleans, and an empty Uint8Array for bytes. For 64-bit integer types, the + * result is "0" when longAsString is true, otherwise 0n. + * + * This is the type's zero value, not a proto2 custom field default. For float + * and double it is +0; isScalarZeroValue treats only +0, not -0, as this value. + */ +function scalarZeroValue(type, longAsString) { + switch (type) { + case ScalarType.STRING: + return ""; + case ScalarType.BOOL: + return false; + case ScalarType.DOUBLE: + case ScalarType.FLOAT: + return 0.0; + case ScalarType.INT64: + case ScalarType.UINT64: + case ScalarType.SFIXED64: + case ScalarType.FIXED64: + case ScalarType.SINT64: + return longAsString ? "0" : protoInt64.zero; + case ScalarType.BYTES: + return new Uint8Array(0); + default: + // Handles INT32, UINT32, SINT32, FIXED32, SFIXED32. + // We do not use individual cases to save a few bytes code size. + return 0; + } +} +/** + * Returns true if the value is the zero value for the given scalar type: `0` + * for numeric types, `false` for booleans, `""` for strings, and an empty + * Uint8Array for bytes. + * + * This is the implicit-presence default check. A singular field with implicit + * presence is treated as unset, and omitted from the wire, when its value is + * the zero value. With explicit presence, or in repeated and map fields, + * presence is structural and this function does not apply. + * + * Note that -0 is NOT a zero value for float and double: under implicit + * presence, +0 is omitted from the wire but -0 is written, following the + * proto3 specification. As a result this can disagree with scalarEquals, which + * compares by value and treats -0 as equal to 0. + */ +function isScalarZeroValue(type, value) { + switch (type) { + case ScalarType.BOOL: + return value === false; + case ScalarType.STRING: + return value === ""; + case ScalarType.BYTES: + return value instanceof Uint8Array && !value.byteLength; + case ScalarType.DOUBLE: + case ScalarType.FLOAT: + // Object.is distinguishes -0 from 0. + return Object.is(value, 0); + default: + // Loose comparison matches 0n, 0 and "0". + return value == 0; + } +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number; +const IMPLICIT$2 = 2; +const unsafeLocal = Symbol.for("reflect unsafe local"); +/** + * Return the selected field of a oneof group. + * + * @private + */ +function unsafeOneofCase( + // biome-ignore lint/suspicious/noExplicitAny: `any` is the best choice for dynamic access + target, + oneof, +) { + const c = target[oneof.localName].case; + if (c === undefined) { + return c; + } + return oneof.fields.find((f) => f.localName === c); +} +/** + * Returns true if the field is set. + * + * @private + */ +function unsafeIsSet( + // biome-ignore lint/suspicious/noExplicitAny: `any` is the best choice for dynamic access + target, + field, +) { + const name = field.localName; + if (field.oneof) { + return target[field.oneof.localName].case === name; + } + if (field.presence != IMPLICIT$2) { + // Fields with explicit presence have properties on the prototype chain + // for default / zero values (except for proto3). + return target[name] !== undefined && Object.prototype.hasOwnProperty.call(target, name); + } + switch (field.fieldKind) { + case "list": + return target[name].length > 0; + case "map": + return Object.keys(target[name]).length > 0; + case "scalar": + return !isScalarZeroValue(field.scalar, target[name]); + case "enum": + return target[name] !== field.enum.values[0].number; + } + throw new Error("message field with implicit presence"); +} +/** + * Returns true if the field is set, but only for singular fields with explicit + * presence (proto2). + * + * @private + */ +function unsafeIsSetExplicit(target, localName) { + return Object.prototype.hasOwnProperty.call(target, localName) && target[localName] !== undefined; +} +/** + * Return a field value, respecting oneof groups. + * + * @private + */ +function unsafeGet(target, field) { + if (field.oneof) { + const oneof = target[field.oneof.localName]; + if (oneof.case === field.localName) { + return oneof.value; + } + return undefined; + } + return target[field.localName]; +} +/** + * Set a field value, respecting oneof groups. + * + * @private + */ +function unsafeSet(target, field, value) { + if (field.oneof) { + target[field.oneof.localName] = { + case: field.localName, + value: value, + }; + } else { + target[field.localName] = value; + } +} +/** + * Resets the field, so that unsafeIsSet() will return false. + * + * @private + */ +function unsafeClear( + // biome-ignore lint/suspicious/noExplicitAny: `any` is the best choice for dynamic access + target, + field, +) { + const name = field.localName; + if (field.oneof) { + const oneofLocalName = field.oneof.localName; + if (target[oneofLocalName].case === name) { + target[oneofLocalName] = { case: undefined }; + } + } else if (field.presence != IMPLICIT$2) { + // Fields with explicit presence have properties on the prototype chain + // for default / zero values (except for proto3). By deleting their own + // property, the field is reset. + delete target[name]; + } else { + switch (field.fieldKind) { + case "map": + target[name] = {}; + break; + case "list": + target[name] = []; + break; + case "enum": + target[name] = field.enum.values[0].number; + break; + case "scalar": + target[name] = scalarZeroValue(field.scalar, field.longAsString); + break; + } + } +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +function isObject(arg) { + return arg !== null && typeof arg == "object" && !Array.isArray(arg); +} +function isReflectList(arg, field) { + var _a, _b, _c, _d; + if (isObject(arg) && unsafeLocal in arg && "add" in arg && "field" in arg && typeof arg.field == "function") { + if (field !== undefined) { + const a = field; + const b = arg.field(); + return ( + a.listKind == b.listKind && + a.scalar === b.scalar && + ((_a = a.message) === null || _a === void 0 ? void 0 : _a.typeName) === ((_b = b.message) === null || _b === void 0 ? void 0 : _b.typeName) && + ((_c = a.enum) === null || _c === void 0 ? void 0 : _c.typeName) === ((_d = b.enum) === null || _d === void 0 ? void 0 : _d.typeName) + ); + } + return true; + } + return false; +} +function isReflectMap(arg, field) { + var _a, _b, _c, _d; + if (isObject(arg) && unsafeLocal in arg && "has" in arg && "field" in arg && typeof arg.field == "function") { + if (field !== undefined) { + const a = field, + b = arg.field(); + return ( + a.mapKey === b.mapKey && + a.mapKind == b.mapKind && + a.scalar === b.scalar && + ((_a = a.message) === null || _a === void 0 ? void 0 : _a.typeName) === ((_b = b.message) === null || _b === void 0 ? void 0 : _b.typeName) && + ((_c = a.enum) === null || _c === void 0 ? void 0 : _c.typeName) === ((_d = b.enum) === null || _d === void 0 ? void 0 : _d.typeName) + ); + } + return true; + } + return false; +} +function isReflectMessage(arg, messageDesc) { + return isObject(arg) && unsafeLocal in arg && "desc" in arg && isObject(arg.desc) && arg.desc.kind === "message" && (messageDesc === undefined || arg.desc.typeName == messageDesc.typeName); +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +function isWrapper(arg) { + return isWrapperTypeName(arg.$typeName); +} +function isWrapperDesc(messageDesc) { + const f = messageDesc.fields[0]; + return isWrapperTypeName(messageDesc.typeName) && f !== undefined && f.fieldKind == "scalar" && f.name == "value" && f.number == 1; +} +function isWrapperTypeName(name) { + return ( + name.startsWith("google.protobuf.") && + ["DoubleValue", "FloatValue", "Int64Value", "UInt64Value", "Int32Value", "UInt32Value", "BoolValue", "StringValue", "BytesValue"].includes(name.substring(16)) + ); +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// bootstrap-inject google.protobuf.Edition.EDITION_PROTO3: const $name: Edition.$localName = $number; +const EDITION_PROTO3$1 = 999; +// bootstrap-inject google.protobuf.Edition.EDITION_PROTO2: const $name: Edition.$localName = $number; +const EDITION_PROTO2$1 = 998; +// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number; +const IMPLICIT$1 = 2; +/** + * Create a new message instance. + * + * The second argument is an optional initializer object, where all fields are + * optional. + */ +function create(schema, init) { + if (isMessage(init, schema)) { + return init; + } + const message = createZeroMessage(schema); + if (init !== undefined) { + initMessage(schema, message, init); + } + return message; +} +/** + * Sets field values from a MessageInitShape on a zero message. + */ +function initMessage(messageDesc, message, init) { + for (const member of messageDesc.members) { + let value = init[member.localName]; + if (value == null) { + // intentionally ignore undefined and null + continue; + } + let field; + if (member.kind == "oneof") { + const oneofField = unsafeOneofCase(init, member); + if (!oneofField) { + continue; + } + field = oneofField; + value = unsafeGet(init, oneofField); + } else { + field = member; + } + switch (field.fieldKind) { + case "message": + value = toMessage(field, value); + break; + case "scalar": + value = initScalar(field, value); + break; + case "list": + value = initList(field, value); + break; + case "map": + value = initMap(field, value); + break; + } + unsafeSet(message, field, value); + } + return message; +} +function initScalar(field, value) { + if (field.scalar == ScalarType.BYTES) { + return toU8Arr(value); + } + return value; +} +function initMap(field, value) { + if (isObject(value)) { + if (field.scalar == ScalarType.BYTES) { + return convertObjectValues(value, toU8Arr); + } + if (field.mapKind == "message") { + return convertObjectValues(value, (val) => toMessage(field, val)); + } + } + return value; +} +function initList(field, value) { + if (Array.isArray(value)) { + if (field.scalar == ScalarType.BYTES) { + return value.map(toU8Arr); + } + if (field.listKind == "message") { + return value.map((item) => toMessage(field, item)); + } + } + return value; +} +function toMessage(field, value) { + if (field.fieldKind == "message" && !field.oneof && isWrapperDesc(field.message)) { + // Types from google/protobuf/wrappers.proto are unwrapped when used in + // a singular field that is not part of a oneof group. + return initScalar(field.message.fields[0], value); + } + if (isObject(value)) { + if (field.message.typeName == "google.protobuf.Struct" && field.parent.typeName !== "google.protobuf.Value") { + // google.protobuf.Struct is represented with JsonObject when used in a + // field, except when used in google.protobuf.Value. + return value; + } + if (!isMessage(value, field.message)) { + return create(field.message, value); + } + } + return value; +} +// converts any ArrayLike to Uint8Array if necessary. +function toU8Arr(value) { + return Array.isArray(value) ? new Uint8Array(value) : value; +} +function convertObjectValues(obj, fn) { + const ret = {}; + for (const entry of Object.entries(obj)) { + ret[entry[0]] = fn(entry[1]); + } + return ret; +} +const tokenZeroMessageField = Symbol(); +const messagePrototypes = new WeakMap(); +/** + * Create a zero message. + */ +function createZeroMessage(desc) { + let msg; + if (!needsPrototypeChain(desc)) { + msg = { + $typeName: desc.typeName, + }; + for (const member of desc.members) { + if (member.kind == "oneof" || member.presence == IMPLICIT$1) { + msg[member.localName] = createZeroField(member); + } + } + } else { + // Support default values and track presence via the prototype chain + const cached = messagePrototypes.get(desc); + let prototype; + let members; + if (cached) { + ({ prototype, members } = cached); + } else { + prototype = {}; + members = new Set(); + for (const member of desc.members) { + if (member.kind == "oneof") { + // we can only put immutable values on the prototype, + // oneof ADTs are mutable + continue; + } + if (member.fieldKind != "scalar" && member.fieldKind != "enum") { + // only scalar and enum values are immutable, map, list, and message + // are not + continue; + } + if (member.presence == IMPLICIT$1) { + // implicit presence tracks field presence by zero values - e.g. 0, false, "", are unset, 1, true, "x" are set. + // message, map, list fields are mutable, and also have IMPLICIT presence. + continue; + } + members.add(member); + prototype[member.localName] = createZeroField(member); + } + messagePrototypes.set(desc, { prototype, members }); + } + msg = Object.create(prototype); + msg.$typeName = desc.typeName; + for (const member of desc.members) { + if (members.has(member)) { + continue; + } + if (member.kind == "field") { + if (member.fieldKind == "message") { + continue; + } + if (member.fieldKind == "scalar" || member.fieldKind == "enum") { + if (member.presence != IMPLICIT$1) { + continue; + } + } + } + msg[member.localName] = createZeroField(member); + } + } + return msg; +} +/** + * Do we need the prototype chain to track field presence? + */ +function needsPrototypeChain(desc) { + switch (desc.file.edition) { + case EDITION_PROTO3$1: + // proto3 always uses implicit presence, we never need the prototype chain. + return false; + case EDITION_PROTO2$1: + // proto2 never uses implicit presence, we always need the prototype chain. + return true; + default: + // If a message uses scalar or enum fields with explicit presence, we need + // the prototype chain to track presence. This rule does not apply to fields + // in a oneof group - they use a different mechanism to track presence. + return desc.fields.some((f) => f.presence != IMPLICIT$1 && f.fieldKind != "message" && !f.oneof); + } +} +/** + * Returns a zero value for oneof groups, and for every field kind except + * messages. Scalar and enum fields can have default values. + */ +function createZeroField(field) { + if (field.kind == "oneof") { + return { case: undefined }; + } + if (field.fieldKind == "list") { + return []; + } + if (field.fieldKind == "map") { + return {}; // Object.create(null) would be desirable here, but is unsupported by react https://react.dev/reference/react/use-server#serializable-parameters-and-return-values + } + if (field.fieldKind == "message") { + return tokenZeroMessageField; + } + const defaultValue = field.getDefaultValue(); + if (defaultValue !== undefined) { + return field.fieldKind == "scalar" && field.longAsString ? defaultValue.toString() : defaultValue; + } + return field.fieldKind == "scalar" ? scalarZeroValue(field.scalar, field.longAsString) : field.enum.values[0].number; +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +class FieldError extends Error { + constructor(fieldOrOneof, message, name = "FieldValueInvalidError") { + super(message); + this.name = name; + this.field = () => fieldOrOneof; + } +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +const symbol = Symbol.for("@bufbuild/protobuf/text-encoding"); +function getTextEncoding() { + if (globalThis[symbol] == undefined) { + const te = new globalThis.TextEncoder(); + const td = new globalThis.TextDecoder(); + let tdStrict; + globalThis[symbol] = { + encodeUtf8(text) { + return te.encode(text); + }, + decodeUtf8(bytes, strict) { + if (strict) { + if (tdStrict === undefined) { + tdStrict = new globalThis.TextDecoder("utf-8", { fatal: true }); + } + return tdStrict.decode(bytes); + } + return td.decode(bytes); + }, + checkUtf8(text) { + try { + encodeURIComponent(text); + return true; + } catch (_) { + return false; + } + }, + }; + } + return globalThis[symbol]; +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Protobuf binary format wire types. + * + * A wire type provides just enough information to find the length of the + * following value. + * + * See https://developers.google.com/protocol-buffers/docs/encoding#structure + */ +var WireType; +(function (WireType) { + /** + * Used for int32, int64, uint32, uint64, sint32, sint64, bool, enum + */ + WireType[(WireType["Varint"] = 0)] = "Varint"; + /** + * Used for fixed64, sfixed64, double. + * Always 8 bytes with little-endian byte order. + */ + WireType[(WireType["Bit64"] = 1)] = "Bit64"; + /** + * Used for string, bytes, embedded messages, packed repeated fields + * + * Only repeated numeric types (types which use the varint, 32-bit, + * or 64-bit wire types) can be packed. In proto3, such fields are + * packed by default. + */ + WireType[(WireType["LengthDelimited"] = 2)] = "LengthDelimited"; + /** + * Start of a tag-delimited aggregate, such as a proto2 group, or a message + * in editions with message_encoding = DELIMITED. + */ + WireType[(WireType["StartGroup"] = 3)] = "StartGroup"; + /** + * End of a tag-delimited aggregate. + */ + WireType[(WireType["EndGroup"] = 4)] = "EndGroup"; + /** + * Used for fixed32, sfixed32, float. + * Always 4 bytes with little-endian byte order. + */ + WireType[(WireType["Bit32"] = 5)] = "Bit32"; +})(WireType || (WireType = {})); +/** + * Maximum value for a 32-bit floating point value (Protobuf FLOAT). + */ +const FLOAT32_MAX = 3.4028234663852886e38; +/** + * Minimum value for a 32-bit floating point value (Protobuf FLOAT). + */ +const FLOAT32_MIN = -34028234663852886e22; +/** + * Maximum value for an unsigned 32-bit integer (Protobuf UINT32, FIXED32). + */ +const UINT32_MAX = 0xffffffff; +/** + * Maximum value for a signed 32-bit integer (Protobuf INT32, SFIXED32, SINT32). + */ +const INT32_MAX = 0x7fffffff; +/** + * Minimum value for a signed 32-bit integer (Protobuf INT32, SFIXED32, SINT32). + */ +const INT32_MIN = -2147483648; +class BinaryReader { + constructor(buf, decodeUtf8 = getTextEncoding().decodeUtf8) { + this.decodeUtf8 = decodeUtf8; + this.varint64 = varint64read; // dirty cast for `this` + /** + * Read a `uint32` field, an unsigned 32 bit varint. + */ + this.uint32 = varint32read; + this.buf = buf; + this.len = buf.length; + this.pos = 0; + this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength); + } + /** + * Reads a tag - field number and wire type. Tags are uint32 varints; values + * that do not fit in uint32 are rejected. + */ + tag() { + const start = this.pos; + const tag = this.uint32(); + const bytesRead = this.pos - start; + if (bytesRead > 5 || (bytesRead == 5 && this.buf[this.pos - 1] > 0x0f)) { + throw new Error("illegal tag: varint overflows uint32"); + } + const fieldNo = tag >>> 3; + const wireType = tag & 7; + if (fieldNo <= 0 || wireType > 5) { + throw new Error("illegal tag: field no " + fieldNo + " wire type " + wireType); + } + return [fieldNo, wireType]; + } + /** + * Skip one element and return the skipped data. + * + * When skipping StartGroup, provide the tags field number to check for + * matching field number in the EndGroup tag. Recursion into nested groups + * is guarded by the `recursionLimit` argument: When the limit is reached, + * this method throws. + */ + skip(wireType, fieldNo, recursionLimit = 100) { + let start = this.pos; + switch (wireType) { + case WireType.Varint: + while (this.buf[this.pos++] & 0x80) { + // ignore + } + break; + // @ts-ignore TS7029: Fallthrough case in switch -- ignore instead of expect-error for compiler settings without noFallthroughCasesInSwitch: true + case WireType.Bit64: + this.pos += 4; + case WireType.Bit32: + this.pos += 4; + break; + case WireType.LengthDelimited: + let len = this.uint32(); + this.pos += len; + break; + case WireType.StartGroup: + if (recursionLimit <= 0) { + throw new Error("maximum recursion depth reached"); + } + for (;;) { + const [fn, wt] = this.tag(); + if (wt === WireType.EndGroup) { + if (fieldNo !== undefined && fn !== fieldNo) { + throw new Error("invalid end group tag"); + } + break; + } + this.skip(wt, fn, recursionLimit - 1); + } + break; + default: + throw new Error("cant skip wire type " + wireType); + } + this.assertBounds(); + return this.buf.subarray(start, this.pos); + } + /** + * Throws error if position in byte array is out of range. + */ + assertBounds() { + if (this.pos > this.len) throw new RangeError("premature EOF"); + } + /** + * Read a `int32` field, a signed 32 bit varint. + */ + int32() { + return this.uint32() | 0; + } + /** + * Read a `sint32` field, a signed, zigzag-encoded 32-bit varint. + */ + sint32() { + let zze = this.uint32(); + // decode zigzag + return (zze >>> 1) ^ -(zze & 1); + } + /** + * Read a `int64` field, a signed 64-bit varint. + */ + int64() { + return protoInt64.dec(...this.varint64()); + } + /** + * Read a `uint64` field, an unsigned 64-bit varint. + */ + uint64() { + return protoInt64.uDec(...this.varint64()); + } + /** + * Read a `sint64` field, a signed, zig-zag-encoded 64-bit varint. + */ + sint64() { + let [lo, hi] = this.varint64(); + // decode zig zag + let s = -(lo & 1); + lo = ((lo >>> 1) | ((hi & 1) << 31)) ^ s; + hi = (hi >>> 1) ^ s; + return protoInt64.dec(lo, hi); + } + /** + * Read a `bool` field, a variant. + */ + bool() { + let [lo, hi] = this.varint64(); + return lo !== 0 || hi !== 0; + } + /** + * Read a `fixed32` field, an unsigned, fixed-length 32-bit integer. + */ + fixed32() { + // biome-ignore lint/suspicious/noAssignInExpressions: no + return this.view.getUint32((this.pos += 4) - 4, true); + } + /** + * Read a `sfixed32` field, a signed, fixed-length 32-bit integer. + */ + sfixed32() { + // biome-ignore lint/suspicious/noAssignInExpressions: no + return this.view.getInt32((this.pos += 4) - 4, true); + } + /** + * Read a `fixed64` field, an unsigned, fixed-length 64 bit integer. + */ + fixed64() { + return protoInt64.uDec(this.sfixed32(), this.sfixed32()); + } + /** + * Read a `fixed64` field, a signed, fixed-length 64-bit integer. + */ + sfixed64() { + return protoInt64.dec(this.sfixed32(), this.sfixed32()); + } + /** + * Read a `float` field, 32-bit floating point number. + */ + float() { + // biome-ignore lint/suspicious/noAssignInExpressions: no + return this.view.getFloat32((this.pos += 4) - 4, true); + } + /** + * Read a `double` field, a 64-bit floating point number. + */ + double() { + // biome-ignore lint/suspicious/noAssignInExpressions: no + return this.view.getFloat64((this.pos += 8) - 8, true); + } + /** + * Read a `bytes` field, length-delimited arbitrary data. + */ + bytes() { + let len = this.uint32(), + start = this.pos; + this.pos += len; + this.assertBounds(); + return this.buf.subarray(start, start + len); + } + /** + * Read a `string` field, length-delimited data converted to UTF-8 text. If + * `strict` is true, throw on invalid UTF-8 instead of substituting U+FFFD. + */ + string(strict) { + return this.decodeUtf8(this.bytes(), strict); + } +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Check whether the given field value is valid for the reflect API. + */ +function checkField(field, value) { + const check = + field.fieldKind == "list" ? isReflectList(value, field) + : field.fieldKind == "map" ? isReflectMap(value, field) + : checkSingular(field, value); + if (check === true) { + return undefined; + } + let reason; + switch (field.fieldKind) { + case "list": + reason = `expected ${formatReflectList(field)}, got ${formatVal(value)}`; + break; + case "map": + reason = `expected ${formatReflectMap(field)}, got ${formatVal(value)}`; + break; + default: { + reason = reasonSingular(field, value, check); + } + } + return new FieldError(field, reason); +} +/** + * Check whether the given list item is valid for the reflect API. + */ +function checkListItem(field, index, value) { + const check = checkSingular(field, value); + if (check !== true) { + return new FieldError(field, `list item #${index + 1}: ${reasonSingular(field, value, check)}`); + } + return undefined; +} +/** + * Check whether the given map key and value are valid for the reflect API. + */ +function checkMapEntry(field, key, value) { + const checkKey = checkScalarValue(key, field.mapKey); + if (checkKey !== true) { + return new FieldError(field, `invalid map key: ${reasonSingular({ scalar: field.mapKey }, key, checkKey)}`); + } + const checkVal = checkSingular(field, value); + if (checkVal !== true) { + return new FieldError(field, `map entry ${formatVal(key)}: ${reasonSingular(field, value, checkVal)}`); + } + return undefined; +} +function checkSingular(field, value) { + if (field.scalar !== undefined) { + return checkScalarValue(value, field.scalar); + } + if (field.enum !== undefined) { + if (field.enum.open) { + // Open enums accept unrecognized values, but enum values are always + // int32 (see https://protobuf.dev/programming-guides/proto3/#enum). + return checkScalarValue(value, ScalarType.INT32); + } + return field.enum.values.some((v) => v.number === value); + } + return isReflectMessage(value, field.message); +} +function checkScalarValue(value, scalar) { + switch (scalar) { + case ScalarType.DOUBLE: + return typeof value == "number"; + case ScalarType.FLOAT: + if (typeof value != "number") { + return false; + } + if (Number.isNaN(value) || !Number.isFinite(value)) { + return true; + } + if (value > FLOAT32_MAX || value < FLOAT32_MIN) { + return `${value.toFixed()} out of range`; + } + return true; + case ScalarType.INT32: + case ScalarType.SFIXED32: + case ScalarType.SINT32: + // signed + if (typeof value !== "number" || !Number.isInteger(value)) { + return false; + } + if (value > INT32_MAX || value < INT32_MIN) { + return `${value.toFixed()} out of range`; + } + return true; + case ScalarType.FIXED32: + case ScalarType.UINT32: + // unsigned + if (typeof value !== "number" || !Number.isInteger(value)) { + return false; + } + if (value > UINT32_MAX || value < 0) { + return `${value.toFixed()} out of range`; + } + return true; + case ScalarType.BOOL: + return typeof value == "boolean"; + case ScalarType.STRING: + if (typeof value != "string") { + return false; + } + return getTextEncoding().checkUtf8(value) || "invalid UTF8"; + case ScalarType.BYTES: + return value instanceof Uint8Array; + case ScalarType.INT64: + case ScalarType.SFIXED64: + case ScalarType.SINT64: + // signed + if (typeof value == "bigint" || typeof value == "number" || (typeof value == "string" && value.length > 0)) { + try { + protoInt64.parse(value); + return true; + } catch (_) { + return `${value} out of range`; + } + } + return false; + case ScalarType.FIXED64: + case ScalarType.UINT64: + // unsigned + if (typeof value == "bigint" || typeof value == "number" || (typeof value == "string" && value.length > 0)) { + try { + protoInt64.uParse(value); + return true; + } catch (_) { + return `${value} out of range`; + } + } + return false; + } +} +function reasonSingular(field, val, details) { + details = typeof details == "string" ? `: ${details}` : `, got ${formatVal(val)}`; + if (field.scalar !== undefined) { + return `expected ${scalarTypeDescription(field.scalar)}` + details; + } + if (field.enum !== undefined) { + return `expected ${field.enum.toString()}` + details; + } + return `expected ${formatReflectMessage(field.message)}` + details; +} +function formatVal(val) { + switch (typeof val) { + case "object": + if (val === null) { + return "null"; + } + if (val instanceof Uint8Array) { + return `Uint8Array(${val.length})`; + } + if (Array.isArray(val)) { + return `Array(${val.length})`; + } + if (isReflectList(val)) { + return formatReflectList(val.field()); + } + if (isReflectMap(val)) { + return formatReflectMap(val.field()); + } + if (isReflectMessage(val)) { + return formatReflectMessage(val.desc); + } + if (isMessage(val)) { + return `message ${val.$typeName}`; + } + return "object"; + case "string": + return val.length > 30 ? "string" : `"${val.split('"').join('\\"')}"`; + case "boolean": + return String(val); + case "number": + return String(val); + case "bigint": + return String(val) + "n"; + default: + // "symbol" | "undefined" | "object" | "function" + return typeof val; + } +} +function formatReflectMessage(desc) { + return `ReflectMessage (${desc.typeName})`; +} +function formatReflectList(field) { + switch (field.listKind) { + case "message": + return `ReflectList (${field.message.toString()})`; + case "enum": + return `ReflectList (${field.enum.toString()})`; + case "scalar": + return `ReflectList (${ScalarType[field.scalar]})`; + } +} +function formatReflectMap(field) { + switch (field.mapKind) { + case "message": + return `ReflectMap (${ScalarType[field.mapKey]}, ${field.message.toString()})`; + case "enum": + return `ReflectMap (${ScalarType[field.mapKey]}, ${field.enum.toString()})`; + case "scalar": + return `ReflectMap (${ScalarType[field.mapKey]}, ${ScalarType[field.scalar]})`; + } +} +function scalarTypeDescription(scalar) { + switch (scalar) { + case ScalarType.STRING: + return "string"; + case ScalarType.BOOL: + return "boolean"; + case ScalarType.INT64: + case ScalarType.SINT64: + case ScalarType.SFIXED64: + return "bigint (int64)"; + case ScalarType.UINT64: + case ScalarType.FIXED64: + return "bigint (uint64)"; + case ScalarType.BYTES: + return "Uint8Array"; + case ScalarType.DOUBLE: + return "number (float64)"; + case ScalarType.FLOAT: + return "number (float32)"; + case ScalarType.FIXED32: + case ScalarType.UINT32: + return "number (uint32)"; + case ScalarType.INT32: + case ScalarType.SFIXED32: + case ScalarType.SINT32: + return "number (int32)"; + } +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Create a ReflectMessage. + */ +function reflect( + messageDesc, + message, + /** + * By default, field values are validated when setting them. For example, + * a value for an uint32 field must be a ECMAScript Number >= 0. + * + * When field values are trusted, performance can be improved by disabling + * checks. + */ + check = true, +) { + return new ReflectMessageImpl(messageDesc, message, check); +} +const messageSortedFields = new WeakMap(); +class ReflectMessageImpl { + get sortedFields() { + const cached = messageSortedFields.get(this.desc); + if (cached) { + return cached; + } + const sortedFields = this.desc.fields.concat().sort((a, b) => a.number - b.number); + messageSortedFields.set(this.desc, sortedFields); + return sortedFields; + } + constructor(messageDesc, message, check = true) { + this.lists = new Map(); + this.maps = new Map(); + this.check = check; + this.desc = messageDesc; + this.message = this[unsafeLocal] = message !== null && message !== void 0 ? message : create(messageDesc); + this.fields = messageDesc.fields; + this.oneofs = messageDesc.oneofs; + this.members = messageDesc.members; + } + findNumber(number) { + if (!this._fieldsByNumber) { + this._fieldsByNumber = new Map(this.desc.fields.map((f) => [f.number, f])); + } + return this._fieldsByNumber.get(number); + } + oneofCase(oneof) { + assertOwn(this.message, oneof); + return unsafeOneofCase(this.message, oneof); + } + isSet(field) { + assertOwn(this.message, field); + return unsafeIsSet(this.message, field); + } + clear(field) { + assertOwn(this.message, field); + unsafeClear(this.message, field); + } + get(field) { + assertOwn(this.message, field); + const value = unsafeGet(this.message, field); + switch (field.fieldKind) { + case "list": + // eslint-disable-next-line no-case-declarations + let list = this.lists.get(field); + if (!list || list[unsafeLocal] !== value) { + this.lists.set( + field, + // biome-ignore lint/suspicious/noAssignInExpressions: no + (list = new ReflectListImpl(field, value, this.check)), + ); + } + return list; + case "map": + let map = this.maps.get(field); + if (!map || map[unsafeLocal] !== value) { + this.maps.set( + field, + // biome-ignore lint/suspicious/noAssignInExpressions: no + (map = new ReflectMapImpl(field, value, this.check)), + ); + } + return map; + case "message": + return messageToReflect(field, value, this.check); + case "scalar": + return value === undefined ? scalarZeroValue(field.scalar, false) : longToReflect(field, value); + case "enum": + return value !== null && value !== void 0 ? value : field.enum.values[0].number; + } + } + set(field, value) { + assertOwn(this.message, field); + if (this.check) { + const err = checkField(field, value); + if (err) { + throw err; + } + } + let local; + if (field.fieldKind == "message") { + local = messageToLocal(field, value); + } else if (isReflectMap(value) || isReflectList(value)) { + local = value[unsafeLocal]; + } else { + local = longToLocal(field, value); + } + unsafeSet(this.message, field, local); + } + getUnknown() { + return this.message.$unknown; + } + setUnknown(value) { + this.message.$unknown = value; + } +} +function assertOwn(owner, member) { + if (member.parent.typeName !== owner.$typeName) { + throw new FieldError(member, `cannot use ${member.toString()} with message ${owner.$typeName}`, "ForeignFieldError"); + } +} +class ReflectListImpl { + field() { + return this._field; + } + get size() { + return this._arr.length; + } + constructor(field, unsafeInput, check) { + this._field = field; + this._arr = this[unsafeLocal] = unsafeInput; + this.check = check; + } + get(index) { + const item = this._arr[index]; + return item === undefined ? undefined : listItemToReflect(this._field, item, this.check); + } + set(index, item) { + if (index < 0 || index >= this._arr.length) { + throw new FieldError(this._field, `list item #${index + 1}: out of range`); + } + if (this.check) { + const err = checkListItem(this._field, index, item); + if (err) { + throw err; + } + } + this._arr[index] = listItemToLocal(this._field, item); + } + add(item) { + if (this.check) { + const err = checkListItem(this._field, this._arr.length, item); + if (err) { + throw err; + } + } + this._arr.push(listItemToLocal(this._field, item)); + return undefined; + } + clear() { + this._arr.splice(0, this._arr.length); + } + [Symbol.iterator]() { + return this.values(); + } + keys() { + return this._arr.keys(); + } + *values() { + for (const item of this._arr) { + yield listItemToReflect(this._field, item, this.check); + } + } + *entries() { + for (let i = 0; i < this._arr.length; i++) { + yield [i, listItemToReflect(this._field, this._arr[i], this.check)]; + } + } +} +class ReflectMapImpl { + constructor(field, unsafeInput, check = true) { + this.obj = this[unsafeLocal] = unsafeInput !== null && unsafeInput !== void 0 ? unsafeInput : {}; + this.check = check; + this._field = field; + } + field() { + return this._field; + } + set(key, value) { + if (this.check) { + const err = checkMapEntry(this._field, key, value); + if (err) { + throw err; + } + } + this.obj[mapKeyToLocal(key)] = mapValueToLocal(this._field, value); + return this; + } + delete(key) { + const k = mapKeyToLocal(key); + const has = Object.prototype.hasOwnProperty.call(this.obj, k); + if (has) { + delete this.obj[k]; + } + return has; + } + clear() { + for (const key of Object.keys(this.obj)) { + delete this.obj[key]; + } + } + get(key) { + let val = this.obj[mapKeyToLocal(key)]; + if (val !== undefined) { + val = mapValueToReflect(this._field, val, this.check); + } + return val; + } + has(key) { + return Object.prototype.hasOwnProperty.call(this.obj, mapKeyToLocal(key)); + } + *keys() { + for (const objKey of Object.keys(this.obj)) { + yield mapKeyToReflect(objKey, this._field.mapKey); + } + } + *entries() { + for (const objEntry of Object.entries(this.obj)) { + yield [mapKeyToReflect(objEntry[0], this._field.mapKey), mapValueToReflect(this._field, objEntry[1], this.check)]; + } + } + [Symbol.iterator]() { + return this.entries(); + } + get size() { + return Object.keys(this.obj).length; + } + *values() { + for (const val of Object.values(this.obj)) { + yield mapValueToReflect(this._field, val, this.check); + } + } + forEach(callbackfn, thisArg) { + for (const mapEntry of this.entries()) { + callbackfn.call(thisArg, mapEntry[1], mapEntry[0], this); + } + } +} +function messageToLocal(field, value) { + if (!isReflectMessage(value)) { + return value; + } + if (isWrapper(value.message) && !field.oneof && field.fieldKind == "message") { + // Types from google/protobuf/wrappers.proto are unwrapped when used in + // a singular field that is not part of a oneof group. + return value.message.value; + } + if (value.desc.typeName == "google.protobuf.Struct" && field.parent.typeName != "google.protobuf.Value") { + // google.protobuf.Struct is represented with JsonObject when used in a + // field, except when used in google.protobuf.Value. + return wktStructToLocal(value.message); + } + return value.message; +} +function messageToReflect(field, value, check) { + if (value !== undefined) { + if (isWrapperDesc(field.message) && !field.oneof && field.fieldKind == "message") { + // Types from google/protobuf/wrappers.proto are unwrapped when used in + // a singular field that is not part of a oneof group. + value = { + $typeName: field.message.typeName, + value: longToReflect(field.message.fields[0], value), + }; + } else if (field.message.typeName == "google.protobuf.Struct" && field.parent.typeName != "google.protobuf.Value" && isObject(value)) { + // google.protobuf.Struct is represented with JsonObject when used in a + // field, except when used in google.protobuf.Value. + value = wktStructToReflect(value); + } + } + return new ReflectMessageImpl(field.message, value, check); +} +function listItemToLocal(field, value) { + if (field.listKind == "message") { + return messageToLocal(field, value); + } + return longToLocal(field, value); +} +function listItemToReflect(field, value, check) { + if (field.listKind == "message") { + return messageToReflect(field, value, check); + } + return longToReflect(field, value); +} +function mapValueToLocal(field, value) { + if (field.mapKind == "message") { + return messageToLocal(field, value); + } + return longToLocal(field, value); +} +function mapValueToReflect(field, value, check) { + if (field.mapKind == "message") { + return messageToReflect(field, value, check); + } + return value; +} +function mapKeyToLocal(key) { + return typeof key == "string" || typeof key == "number" ? key : String(key); +} +/** + * Converts a map key (any scalar value except float, double, or bytes) from its + * representation in a message (string or number, the only possible object key + * types) to the closest possible type in ECMAScript. + */ +function mapKeyToReflect(key, type) { + switch (type) { + case ScalarType.STRING: + return key; + case ScalarType.INT32: + case ScalarType.FIXED32: + case ScalarType.UINT32: + case ScalarType.SFIXED32: + case ScalarType.SINT32: { + const n = Number.parseInt(key); + if (Number.isFinite(n)) { + return n; + } + break; + } + case ScalarType.BOOL: + switch (key) { + case "true": + return true; + case "false": + return false; + } + break; + case ScalarType.UINT64: + case ScalarType.FIXED64: + try { + return protoInt64.uParse(key); + } catch (_a) { + // + } + break; + default: + // INT64, SFIXED64, SINT64 + try { + return protoInt64.parse(key); + } catch (_b) { + // + } + break; + } + return key; +} +function longToReflect(field, value) { + switch (field.scalar) { + case ScalarType.INT64: + case ScalarType.SFIXED64: + case ScalarType.SINT64: + if ("longAsString" in field && field.longAsString && typeof value == "string") { + value = protoInt64.parse(value); + } + break; + case ScalarType.FIXED64: + case ScalarType.UINT64: + if ("longAsString" in field && field.longAsString && typeof value == "string") { + value = protoInt64.uParse(value); + } + break; + } + return value; +} +function longToLocal(field, value) { + switch (field.scalar) { + case ScalarType.INT64: + case ScalarType.SFIXED64: + case ScalarType.SINT64: + if ("longAsString" in field && field.longAsString) { + value = String(value); + } else if (typeof value == "string" || typeof value == "number") { + value = protoInt64.parse(value); + } + break; + case ScalarType.FIXED64: + case ScalarType.UINT64: + if ("longAsString" in field && field.longAsString) { + value = String(value); + } else if (typeof value == "string" || typeof value == "number") { + value = protoInt64.uParse(value); + } + break; + } + return value; +} +function wktStructToReflect(json) { + const struct = { + $typeName: "google.protobuf.Struct", + fields: {}, + }; + if (isObject(json)) { + for (const [k, v] of Object.entries(json)) { + struct.fields[k] = wktValueToReflect(v); + } + } + return struct; +} +function wktStructToLocal(val) { + const json = {}; + for (const [k, v] of Object.entries(val.fields)) { + json[k] = wktValueToLocal(v); + } + return json; +} +function wktValueToLocal(val) { + switch (val.kind.case) { + case "structValue": + return wktStructToLocal(val.kind.value); + case "listValue": + return val.kind.value.values.map(wktValueToLocal); + case "nullValue": + case undefined: + return null; + default: + return val.kind.value; + } +} +function wktValueToReflect(json) { + const value = { + $typeName: "google.protobuf.Value", + kind: { case: undefined }, + }; + switch (typeof json) { + case "number": + value.kind = { case: "numberValue", value: json }; + break; + case "string": + value.kind = { case: "stringValue", value: json }; + break; + case "boolean": + value.kind = { case: "boolValue", value: json }; + break; + case "object": + if (json === null) { + const nullValue = 0; + value.kind = { case: "nullValue", value: nullValue }; + } else if (Array.isArray(json)) { + const listValue = { + $typeName: "google.protobuf.ListValue", + values: [], + }; + if (Array.isArray(json)) { + for (const e of json) { + listValue.values.push(wktValueToReflect(e)); + } + } + value.kind = { + case: "listValue", + value: listValue, + }; + } else { + value.kind = { + case: "structValue", + value: wktStructToReflect(json), + }; + } + break; + } + return value; +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Decodes a base64 string to a byte array. + * + * - ignores white-space, including line breaks and tabs + * - allows inner padding (can decode concatenated base64 strings) + * - does not require padding + * - understands base64url encoding: + * "-" instead of "+", + * "_" instead of "/", + * no padding + */ +function base64Decode(base64Str) { + const table = getDecodeTable(); + // estimate byte size, not accounting for inner padding and whitespace + let es = (base64Str.length * 3) / 4; + if (base64Str[base64Str.length - 2] == "=") es -= 2; + else if (base64Str[base64Str.length - 1] == "=") es -= 1; + let bytes = new Uint8Array(es), + bytePos = 0, // position in byte array + groupPos = 0, // position in base64 group + b, // current byte + p = 0; // previous byte + for (let i = 0; i < base64Str.length; i++) { + b = table[base64Str.charCodeAt(i)]; + if (b === undefined) { + switch (base64Str[i]) { + // @ts-ignore TS7029: Fallthrough case in switch -- ignore instead of expect-error for compiler settings without noFallthroughCasesInSwitch: true + case "=": + groupPos = 0; // reset state when padding found + case "\n": + case "\r": + case "\t": + case " ": + continue; // skip white-space, and padding + default: + throw Error("invalid base64 string"); + } + } + switch (groupPos) { + case 0: + p = b; + groupPos = 1; + break; + case 1: + bytes[bytePos++] = (p << 2) | ((b & 48) >> 4); + p = b; + groupPos = 2; + break; + case 2: + bytes[bytePos++] = ((p & 15) << 4) | ((b & 60) >> 2); + p = b; + groupPos = 3; + break; + case 3: + bytes[bytePos++] = ((p & 3) << 6) | b; + groupPos = 0; + break; + } + } + if (groupPos == 1) throw Error("invalid base64 string"); + return bytes.subarray(0, bytePos); +} +/** + * Encode a byte array to a base64 string. + * + * By default, this function uses the standard base64 encoding with padding. + * + * To encode without padding, use encoding = "std_raw". + * + * To encode with the URL encoding, use encoding = "url", which replaces the + * characters +/ by their URL-safe counterparts -_, and omits padding. + */ +function base64Encode(bytes, encoding = "std") { + const table = getEncodeTable(encoding); + const pad = encoding == "std"; + let base64 = "", + groupPos = 0, // position in base64 group + b, // current byte + p = 0; // carry over from previous byte + for (let i = 0; i < bytes.length; i++) { + b = bytes[i]; + switch (groupPos) { + case 0: + base64 += table[b >> 2]; + p = (b & 3) << 4; + groupPos = 1; + break; + case 1: + base64 += table[p | (b >> 4)]; + p = (b & 15) << 2; + groupPos = 2; + break; + case 2: + base64 += table[p | (b >> 6)]; + base64 += table[b & 63]; + groupPos = 0; + break; + } + } + // add output padding + if (groupPos) { + base64 += table[p]; + if (pad) { + base64 += "="; + if (groupPos == 1) base64 += "="; + } + } + return base64; +} +// lookup table from base64 character to byte +let encodeTableStd; +let encodeTableUrl; +// lookup table from base64 character *code* to byte because lookup by number is fast +let decodeTable; +function getEncodeTable(encoding) { + if (!encodeTableStd) { + encodeTableStd = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""); + encodeTableUrl = encodeTableStd.slice(0, -2).concat("-", "_"); + } + return encoding == "url" ? + // biome-ignore lint/style/noNonNullAssertion: TS fails to narrow down + encodeTableUrl + : encodeTableStd; +} +function getDecodeTable() { + if (!decodeTable) { + decodeTable = []; + const encodeTable = getEncodeTable("std"); + for (let i = 0; i < encodeTable.length; i++) decodeTable[encodeTable[i].charCodeAt(0)] = i; + // support base64url variants + decodeTable["-".charCodeAt(0)] = encodeTable.indexOf("+"); + decodeTable["_".charCodeAt(0)] = encodeTable.indexOf("/"); + } + return decodeTable; +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Return a fully-qualified name for a Protobuf descriptor. + * For a file descriptor, return the original file path. + * + * See https://protobuf.com/docs/language-spec#fully-qualified-names + */ +/** + * Converts snake_case to protoCamelCase according to the convention + * used by protoc to convert a field name to a JSON name. + * + * See https://protobuf.com/docs/language-spec#default-json-names + * + * The function protoSnakeCase provides the reverse. + */ +function protoCamelCase(snakeCase) { + let capNext = false; + const b = []; + for (let i = 0; i < snakeCase.length; i++) { + let c = snakeCase.charAt(i); + switch (c) { + case "_": + capNext = true; + break; + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + case "9": + b.push(c); + capNext = false; + break; + default: + if (capNext) { + capNext = false; + c = c.toUpperCase(); + } + b.push(c); + break; + } + } + return b.join(""); +} +/** + * Names that cannot be used for object properties because they are reserved + * by built-in JavaScript properties. + */ +const reservedObjectProperties = new Set([ + // names reserved by JavaScript + "constructor", + "toString", + "toJSON", + "valueOf", +]); +/** + * Escapes names that are reserved for ECMAScript built-in object properties. + * + * Also see safeIdentifier() from @bufbuild/protoplugin. + */ +function safeObjectProperty(name) { + return reservedObjectProperties.has(name) ? name + "$" : name; +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * @private + */ +function restoreJsonNames(message) { + for (const f of message.field) { + if (!unsafeIsSetExplicit(f, "jsonName")) { + f.jsonName = protoCamelCase(f.name); + } + } + message.nestedType.forEach(restoreJsonNames); +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Parse an enum value from the Protobuf text format. + * + * @private + */ +function parseTextFormatEnumValue(descEnum, value) { + const enumValue = descEnum.values.find((v) => v.name === value); + if (!enumValue) { + throw new Error(`cannot parse ${descEnum} default value: ${value}`); + } + return enumValue.number; +} +/** + * Parse a scalar value from the Protobuf text format. + * + * @private + */ +function parseTextFormatScalarValue(type, value) { + switch (type) { + case ScalarType.STRING: + return value; + case ScalarType.BYTES: { + const u = unescapeBytesDefaultValue(value); + if (u === false) { + throw new Error(`cannot parse ${ScalarType[type]} default value: ${value}`); + } + return u; + } + case ScalarType.INT64: + case ScalarType.SFIXED64: + case ScalarType.SINT64: + return protoInt64.parse(value); + case ScalarType.UINT64: + case ScalarType.FIXED64: + return protoInt64.uParse(value); + case ScalarType.DOUBLE: + case ScalarType.FLOAT: + switch (value) { + case "inf": + return Number.POSITIVE_INFINITY; + case "-inf": + return Number.NEGATIVE_INFINITY; + case "nan": + return Number.NaN; + default: + return parseFloat(value); + } + case ScalarType.BOOL: + return value === "true"; + case ScalarType.INT32: + case ScalarType.UINT32: + case ScalarType.SINT32: + case ScalarType.FIXED32: + case ScalarType.SFIXED32: + return parseInt(value, 10); + } +} +/** + * Parses a text-encoded default value (proto2) of a BYTES field. + */ +function unescapeBytesDefaultValue(str) { + const b = []; + const input = { + tail: str, + c: "", + next() { + if (this.tail.length == 0) { + return false; + } + this.c = this.tail[0]; + this.tail = this.tail.substring(1); + return true; + }, + take(n) { + if (this.tail.length >= n) { + const r = this.tail.substring(0, n); + this.tail = this.tail.substring(n); + return r; + } + return false; + }, + }; + while (input.next()) { + switch (input.c) { + case "\\": + if (input.next()) { + switch (input.c) { + case "\\": + b.push(input.c.charCodeAt(0)); + break; + case "b": + b.push(0x08); + break; + case "f": + b.push(0x0c); + break; + case "n": + b.push(0x0a); + break; + case "r": + b.push(0x0d); + break; + case "t": + b.push(0x09); + break; + case "v": + b.push(0x0b); + break; + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": { + const s = input.c; + const t = input.take(2); + if (t === false) { + return false; + } + const n = parseInt(s + t, 8); + if (Number.isNaN(n)) { + return false; + } + b.push(n); + break; + } + case "x": { + const s = input.c; + const t = input.take(2); + if (t === false) { + return false; + } + const n = parseInt(s + t, 16); + if (Number.isNaN(n)) { + return false; + } + b.push(n); + break; + } + case "u": { + const s = input.c; + const t = input.take(4); + if (t === false) { + return false; + } + const n = parseInt(s + t, 16); + if (Number.isNaN(n)) { + return false; + } + const chunk = new Uint8Array(4); + const view = new DataView(chunk.buffer); + view.setInt32(0, n, true); + b.push(chunk[0], chunk[1], chunk[2], chunk[3]); + break; + } + case "U": { + const s = input.c; + const t = input.take(8); + if (t === false) { + return false; + } + const tc = protoInt64.uEnc(s + t); + const chunk = new Uint8Array(8); + const view = new DataView(chunk.buffer); + view.setInt32(0, tc.lo, true); + view.setInt32(4, tc.hi, true); + b.push(chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7]); + break; + } + } + } + break; + default: + b.push(input.c.charCodeAt(0)); + } + } + return new Uint8Array(b); +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Iterate over all types - enumerations, extensions, services, messages - + * and enumerations, extensions and messages nested in messages. + */ +function* nestedTypes(desc) { + switch (desc.kind) { + case "file": + for (const message of desc.messages) { + yield message; + yield* nestedTypes(message); + } + yield* desc.enums; + yield* desc.services; + yield* desc.extensions; + break; + case "message": + for (const message of desc.nestedMessages) { + yield message; + yield* nestedTypes(message); + } + yield* desc.nestedEnums; + yield* desc.nestedExtensions; + break; + } +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +function createFileRegistry(...args) { + const registry = createBaseRegistry(); + if (!args.length) { + return registry; + } + if ("$typeName" in args[0] && args[0].$typeName == "google.protobuf.FileDescriptorSet") { + for (const file of args[0].file) { + addFile(file, registry); + } + return registry; + } + if ("$typeName" in args[0]) { + const input = args[0]; + const resolve = args[1]; + const seen = new Set(); + function recurseDeps(file) { + const deps = []; + for (const protoFileName of file.dependency) { + if (registry.getFile(protoFileName) != undefined) { + continue; + } + if (seen.has(protoFileName)) { + continue; + } + const dep = resolve(protoFileName); + if (!dep) { + throw new Error(`Unable to resolve ${protoFileName}, imported by ${file.name}`); + } + if ("kind" in dep) { + registry.addFile(dep, false, true); + } else { + seen.add(dep.name); + deps.push(dep); + } + } + return deps.concat(...deps.map(recurseDeps)); + } + for (const file of [input, ...recurseDeps(input)].reverse()) { + addFile(file, registry); + } + } else { + for (const fileReg of args) { + for (const file of fileReg.files) { + registry.addFile(file); + } + } + } + return registry; +} +/** + * @private + */ +function createBaseRegistry() { + const types = new Map(); + const extendees = new Map(); + const files = new Map(); + return { + kind: "registry", + types, + extendees, + [Symbol.iterator]() { + return types.values(); + }, + get files() { + return files.values(); + }, + addFile(file, skipTypes, withDeps) { + files.set(file.proto.name, file); + if (!skipTypes) { + for (const type of nestedTypes(file)) { + this.add(type); + } + } + if (withDeps) { + for (const f of file.dependencies) { + this.addFile(f, skipTypes, withDeps); + } + } + }, + add(desc) { + if (desc.kind == "extension") { + let numberToExt = extendees.get(desc.extendee.typeName); + if (!numberToExt) { + extendees.set( + desc.extendee.typeName, + // biome-ignore lint/suspicious/noAssignInExpressions: no + (numberToExt = new Map()), + ); + } + numberToExt.set(desc.number, desc); + } + types.set(desc.typeName, desc); + }, + get(typeName) { + return types.get(typeName); + }, + getFile(fileName) { + return files.get(fileName); + }, + getMessage(typeName) { + const t = types.get(typeName); + return (t === null || t === void 0 ? void 0 : t.kind) == "message" ? t : undefined; + }, + getEnum(typeName) { + const t = types.get(typeName); + return (t === null || t === void 0 ? void 0 : t.kind) == "enum" ? t : undefined; + }, + getExtension(typeName) { + const t = types.get(typeName); + return (t === null || t === void 0 ? void 0 : t.kind) == "extension" ? t : undefined; + }, + getExtensionFor(extendee, no) { + var _a; + return (_a = extendees.get(extendee.typeName)) === null || _a === void 0 ? void 0 : _a.get(no); + }, + getService(typeName) { + const t = types.get(typeName); + return (t === null || t === void 0 ? void 0 : t.kind) == "service" ? t : undefined; + }, + }; +} +// bootstrap-inject google.protobuf.Edition.EDITION_PROTO2: const $name: Edition.$localName = $number; +const EDITION_PROTO2 = 998; +// bootstrap-inject google.protobuf.Edition.EDITION_PROTO3: const $name: Edition.$localName = $number; +const EDITION_PROTO3 = 999; +// bootstrap-inject google.protobuf.Edition.EDITION_UNSTABLE: const $name: Edition.$localName = $number; +const EDITION_UNSTABLE = 9999; +// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_STRING: const $name: FieldDescriptorProto_Type.$localName = $number; +const TYPE_STRING = 9; +// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_GROUP: const $name: FieldDescriptorProto_Type.$localName = $number; +const TYPE_GROUP = 10; +// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_MESSAGE: const $name: FieldDescriptorProto_Type.$localName = $number; +const TYPE_MESSAGE = 11; +// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_BYTES: const $name: FieldDescriptorProto_Type.$localName = $number; +const TYPE_BYTES = 12; +// bootstrap-inject google.protobuf.FieldDescriptorProto.Type.TYPE_ENUM: const $name: FieldDescriptorProto_Type.$localName = $number; +const TYPE_ENUM = 14; +// bootstrap-inject google.protobuf.FieldDescriptorProto.Label.LABEL_REPEATED: const $name: FieldDescriptorProto_Label.$localName = $number; +const LABEL_REPEATED = 3; +// bootstrap-inject google.protobuf.FieldDescriptorProto.Label.LABEL_REQUIRED: const $name: FieldDescriptorProto_Label.$localName = $number; +const LABEL_REQUIRED = 2; +// bootstrap-inject google.protobuf.FieldOptions.JSType.JS_STRING: const $name: FieldOptions_JSType.$localName = $number; +const JS_STRING = 1; +// bootstrap-inject google.protobuf.MethodOptions.IdempotencyLevel.IDEMPOTENCY_UNKNOWN: const $name: MethodOptions_IdempotencyLevel.$localName = $number; +const IDEMPOTENCY_UNKNOWN = 0; +// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.EXPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number; +const EXPLICIT = 1; +// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number; +const IMPLICIT = 2; +// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.LEGACY_REQUIRED: const $name: FeatureSet_FieldPresence.$localName = $number; +const LEGACY_REQUIRED = 3; +// bootstrap-inject google.protobuf.FeatureSet.RepeatedFieldEncoding.PACKED: const $name: FeatureSet_RepeatedFieldEncoding.$localName = $number; +const PACKED = 1; +// bootstrap-inject google.protobuf.FeatureSet.MessageEncoding.DELIMITED: const $name: FeatureSet_MessageEncoding.$localName = $number; +const DELIMITED = 2; +// bootstrap-inject google.protobuf.FeatureSet.EnumType.OPEN: const $name: FeatureSet_EnumType.$localName = $number; +const OPEN = 1; +// bootstrap-inject google.protobuf.FeatureSet.Utf8Validation.VERIFY: const $name: FeatureSet_Utf8Validation.$localName = $number; +const VERIFY = 2; +// biome-ignore format: want this to read well +// bootstrap-inject defaults: EDITION_PROTO2 to EDITION_2024: export const minimumEdition: SupportedEdition = $minimumEdition, maximumEdition: SupportedEdition = $maximumEdition; +// generated from protoc v34.1 +const maximumEdition = 1001; +const featureDefaults = { + // EDITION_PROTO2 + 998: { + fieldPresence: 1, // EXPLICIT, + enumType: 2, // CLOSED, + repeatedFieldEncoding: 2, // EXPANDED, + utf8Validation: 3, // NONE, + messageEncoding: 1, // LENGTH_PREFIXED, + jsonFormat: 2, // LEGACY_BEST_EFFORT, + enforceNamingStyle: 2, // STYLE_LEGACY, + defaultSymbolVisibility: 1, // EXPORT_ALL, + }, + // EDITION_PROTO3 + 999: { + fieldPresence: 2, // IMPLICIT, + enumType: 1, // OPEN, + repeatedFieldEncoding: 1, // PACKED, + utf8Validation: 2, // VERIFY, + messageEncoding: 1, // LENGTH_PREFIXED, + jsonFormat: 1, // ALLOW, + enforceNamingStyle: 2, // STYLE_LEGACY, + defaultSymbolVisibility: 1, // EXPORT_ALL, + }, + // EDITION_2023 + 1000: { + fieldPresence: 1, // EXPLICIT, + enumType: 1, // OPEN, + repeatedFieldEncoding: 1, // PACKED, + utf8Validation: 2, // VERIFY, + messageEncoding: 1, // LENGTH_PREFIXED, + jsonFormat: 1, // ALLOW, + enforceNamingStyle: 2, // STYLE_LEGACY, + defaultSymbolVisibility: 1, // EXPORT_ALL, + }, + // EDITION_2024 + 1001: { + fieldPresence: 1, // EXPLICIT, + enumType: 1, // OPEN, + repeatedFieldEncoding: 1, // PACKED, + utf8Validation: 2, // VERIFY, + messageEncoding: 1, // LENGTH_PREFIXED, + jsonFormat: 1, // ALLOW, + enforceNamingStyle: 1, // STYLE2024, + defaultSymbolVisibility: 2, // EXPORT_TOP_LEVEL, + }, +}; +/** + * Create a descriptor for a file, add it to the registry. + */ +function addFile(proto, reg) { + var _a, _b; + const file = { + kind: "file", + proto, + deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false, + edition: getFileEdition(proto), + name: proto.name.replace(/\.proto$/, ""), + dependencies: findFileDependencies(proto, reg), + enums: [], + messages: [], + extensions: [], + services: [], + toString() { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- we asserted above + return `file ${proto.name}`; + }, + }; + const mapEntriesStore = new Map(); + const mapEntries = { + get(typeName) { + return mapEntriesStore.get(typeName); + }, + add(desc) { + var _a; + assert$1(((_a = desc.proto.options) === null || _a === void 0 ? void 0 : _a.mapEntry) === true); + mapEntriesStore.set(desc.typeName, desc); + }, + }; + for (const enumProto of proto.enumType) { + addEnum(enumProto, file, undefined, reg); + } + for (const messageProto of proto.messageType) { + addMessage(messageProto, file, undefined, reg, mapEntries); + } + for (const serviceProto of proto.service) { + addService(serviceProto, file, reg); + } + addExtensions(file, reg); + for (const mapEntry of mapEntriesStore.values()) { + // to create a map field, we need access to the map entry's fields + addFields(mapEntry, reg, mapEntries); + } + for (const message of file.messages) { + addFields(message, reg, mapEntries); + addExtensions(message, reg); + } + reg.addFile(file, true); +} +/** + * Create descriptors for extensions, and add them to the message / file, + * and to our cart. + * Recurses into nested types. + */ +function addExtensions(desc, reg) { + switch (desc.kind) { + case "file": + for (const proto of desc.proto.extension) { + const ext = newField(proto, desc, reg); + desc.extensions.push(ext); + reg.add(ext); + } + break; + case "message": + for (const proto of desc.proto.extension) { + const ext = newField(proto, desc, reg); + desc.nestedExtensions.push(ext); + reg.add(ext); + } + for (const message of desc.nestedMessages) { + addExtensions(message, reg); + } + break; + } +} +/** + * Create descriptors for fields and oneof groups, and add them to the message. + * Recurses into nested types. + */ +function addFields(message, reg, mapEntries) { + const allOneofs = message.proto.oneofDecl.map((proto) => newOneof(proto, message)); + const oneofsSeen = new Set(); + for (const proto of message.proto.field) { + const oneof = findOneof(proto, allOneofs); + const field = newField(proto, message, reg, oneof, mapEntries); + message.fields.push(field); + message.field[field.localName] = field; + if (oneof === undefined) { + message.members.push(field); + } else { + oneof.fields.push(field); + if (!oneofsSeen.has(oneof)) { + oneofsSeen.add(oneof); + message.members.push(oneof); + } + } + } + for (const oneof of allOneofs.filter((o) => oneofsSeen.has(o))) { + message.oneofs.push(oneof); + } + for (const child of message.nestedMessages) { + addFields(child, reg, mapEntries); + } +} +/** + * Create a descriptor for an enumeration, and add it our cart and to the + * parent type, if any. + */ +function addEnum(proto, file, parent, reg) { + var _a, _b, _c, _d, _e; + const sharedPrefix = findEnumSharedPrefix(proto.name, proto.value); + const desc = { + kind: "enum", + proto, + deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false, + file, + parent, + open: true, + name: proto.name, + typeName: makeTypeName(proto, parent, file), + value: {}, + values: [], + sharedPrefix, + toString() { + return `enum ${this.typeName}`; + }, + }; + desc.open = isEnumOpen(desc); + reg.add(desc); + for (const p of proto.value) { + const name = p.name; + desc.values.push( + // biome-ignore lint/suspicious/noAssignInExpressions: no + (desc.value[p.number] = { + kind: "enum_value", + proto: p, + deprecated: (_d = (_c = p.options) === null || _c === void 0 ? void 0 : _c.deprecated) !== null && _d !== void 0 ? _d : false, + parent: desc, + name, + localName: safeObjectProperty(sharedPrefix == undefined ? name : name.substring(sharedPrefix.length)), + number: p.number, + toString() { + return `enum value ${desc.typeName}.${name}`; + }, + }), + ); + } + ((_e = parent === null || parent === void 0 ? void 0 : parent.nestedEnums) !== null && _e !== void 0 ? _e : file.enums).push(desc); +} +/** + * Create a descriptor for a message, including nested types, and add it to our + * cart. Note that this does not create descriptors fields. + */ +function addMessage(proto, file, parent, reg, mapEntries) { + var _a, _b, _c, _d; + const desc = { + kind: "message", + proto, + deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false, + file, + parent, + name: proto.name, + typeName: makeTypeName(proto, parent, file), + fields: [], + field: {}, + oneofs: [], + members: [], + nestedEnums: [], + nestedMessages: [], + nestedExtensions: [], + toString() { + return `message ${this.typeName}`; + }, + }; + if (((_c = proto.options) === null || _c === void 0 ? void 0 : _c.mapEntry) === true) { + mapEntries.add(desc); + } else { + ((_d = parent === null || parent === void 0 ? void 0 : parent.nestedMessages) !== null && _d !== void 0 ? _d : file.messages).push(desc); + reg.add(desc); + } + for (const enumProto of proto.enumType) { + addEnum(enumProto, file, desc, reg); + } + for (const messageProto of proto.nestedType) { + addMessage(messageProto, file, desc, reg, mapEntries); + } +} +/** + * Create a descriptor for a service, including methods, and add it to our + * cart. + */ +function addService(proto, file, reg) { + var _a, _b; + const desc = { + kind: "service", + proto, + deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false, + file, + name: proto.name, + typeName: makeTypeName(proto, undefined, file), + methods: [], + method: {}, + toString() { + return `service ${this.typeName}`; + }, + }; + file.services.push(desc); + reg.add(desc); + for (const methodProto of proto.method) { + const method = newMethod(methodProto, desc, reg); + desc.methods.push(method); + desc.method[method.localName] = method; + } +} +/** + * Create a descriptor for a method. + */ +function newMethod(proto, parent, reg) { + var _a, _b, _c, _d; + let methodKind; + if (proto.clientStreaming && proto.serverStreaming) { + methodKind = "bidi_streaming"; + } else if (proto.clientStreaming) { + methodKind = "client_streaming"; + } else if (proto.serverStreaming) { + methodKind = "server_streaming"; + } else { + methodKind = "unary"; + } + const input = reg.getMessage(trimLeadingDot(proto.inputType)); + const output = reg.getMessage(trimLeadingDot(proto.outputType)); + assert$1(input, `invalid MethodDescriptorProto: input_type ${proto.inputType} not found`); + assert$1(output, `invalid MethodDescriptorProto: output_type ${proto.inputType} not found`); + const name = proto.name; + return { + kind: "rpc", + proto, + deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false, + parent, + name, + localName: safeObjectProperty(name.length ? safeObjectProperty(name[0].toLowerCase() + name.substring(1)) : name), + methodKind, + input, + output, + idempotency: (_d = (_c = proto.options) === null || _c === void 0 ? void 0 : _c.idempotencyLevel) !== null && _d !== void 0 ? _d : IDEMPOTENCY_UNKNOWN, + toString() { + return `rpc ${parent.typeName}.${name}`; + }, + }; +} +/** + * Create a descriptor for a oneof group. + */ +function newOneof(proto, parent) { + return { + kind: "oneof", + proto, + deprecated: false, + parent, + fields: [], + name: proto.name, + localName: safeObjectProperty(protoCamelCase(proto.name)), + toString() { + return `oneof ${parent.typeName}.${this.name}`; + }, + }; +} +function newField(proto, parentOrFile, reg, oneof, mapEntries) { + var _a, _b, _c; + const isExtension = mapEntries === undefined; + const field = { + kind: "field", + proto, + deprecated: (_b = (_a = proto.options) === null || _a === void 0 ? void 0 : _a.deprecated) !== null && _b !== void 0 ? _b : false, + name: proto.name, + number: proto.number, + scalar: undefined, + message: undefined, + enum: undefined, + presence: getFieldPresence(proto, oneof, isExtension, parentOrFile), + utf8Validation: isUtf8Validated(proto, parentOrFile), + listKind: undefined, + mapKind: undefined, + mapKey: undefined, + delimitedEncoding: undefined, + packed: undefined, + longAsString: false, + getDefaultValue: undefined, + }; + if (isExtension) { + // extension field + const file = parentOrFile.kind == "file" ? parentOrFile : parentOrFile.file; + const parent = parentOrFile.kind == "file" ? undefined : parentOrFile; + const typeName = makeTypeName(proto, parent, file); + field.kind = "extension"; + field.file = file; + field.parent = parent; + field.oneof = undefined; + field.typeName = typeName; + field.jsonName = `[${typeName}]`; // option json_name is not allowed on extension fields + field.toString = () => `extension ${typeName}`; + const extendee = reg.getMessage(trimLeadingDot(proto.extendee)); + assert$1(extendee, `invalid FieldDescriptorProto: extendee ${proto.extendee} not found`); + field.extendee = extendee; + } else { + // regular field + const parent = parentOrFile; + assert$1(parent.kind == "message"); + field.parent = parent; + field.oneof = oneof; + field.localName = oneof ? protoCamelCase(proto.name) : safeObjectProperty(protoCamelCase(proto.name)); + field.jsonName = proto.jsonName; + field.toString = () => `field ${parent.typeName}.${proto.name}`; + } + const label = proto.label; + const type = proto.type; + const jstype = (_c = proto.options) === null || _c === void 0 ? void 0 : _c.jstype; + if (label === LABEL_REPEATED) { + // list or map field + const mapEntry = + type == TYPE_MESSAGE ? + mapEntries === null || mapEntries === void 0 ? + void 0 + : mapEntries.get(trimLeadingDot(proto.typeName)) + : undefined; + if (mapEntry) { + // map field + field.fieldKind = "map"; + const { key, value } = findMapEntryFields(mapEntry); + field.mapKey = key.scalar; + field.mapKind = value.fieldKind; + field.message = value.message; + field.delimitedEncoding = false; // map fields are always LENGTH_PREFIXED + field.enum = value.enum; + field.scalar = value.scalar; + return field; + } + // list field + field.fieldKind = "list"; + switch (type) { + case TYPE_MESSAGE: + case TYPE_GROUP: + field.listKind = "message"; + field.message = reg.getMessage(trimLeadingDot(proto.typeName)); + assert$1(field.message); + field.delimitedEncoding = isDelimitedEncoding(proto, parentOrFile); + break; + case TYPE_ENUM: + field.listKind = "enum"; + field.enum = reg.getEnum(trimLeadingDot(proto.typeName)); + assert$1(field.enum); + break; + default: + field.listKind = "scalar"; + field.scalar = type; + field.longAsString = jstype == JS_STRING; + break; + } + field.packed = isPackedField(proto, parentOrFile); + return field; + } + // singular + switch (type) { + case TYPE_MESSAGE: + case TYPE_GROUP: + field.fieldKind = "message"; + field.message = reg.getMessage(trimLeadingDot(proto.typeName)); + assert$1(field.message, `invalid FieldDescriptorProto: type_name ${proto.typeName} not found`); + field.delimitedEncoding = isDelimitedEncoding(proto, parentOrFile); + field.getDefaultValue = () => undefined; + break; + case TYPE_ENUM: { + const enumeration = reg.getEnum(trimLeadingDot(proto.typeName)); + assert$1(enumeration !== undefined, `invalid FieldDescriptorProto: type_name ${proto.typeName} not found`); + field.fieldKind = "enum"; + field.enum = reg.getEnum(trimLeadingDot(proto.typeName)); + field.getDefaultValue = () => { + return unsafeIsSetExplicit(proto, "defaultValue") ? parseTextFormatEnumValue(enumeration, proto.defaultValue) : undefined; + }; + break; + } + default: { + field.fieldKind = "scalar"; + field.scalar = type; + field.longAsString = jstype == JS_STRING; + field.getDefaultValue = () => { + return unsafeIsSetExplicit(proto, "defaultValue") ? parseTextFormatScalarValue(type, proto.defaultValue) : undefined; + }; + break; + } + } + return field; +} +/** + * Parse the "syntax" and "edition" fields, returning one of the supported + * editions. + */ +function getFileEdition(proto) { + switch (proto.syntax) { + case "": + case "proto2": + return EDITION_PROTO2; + case "proto3": + return EDITION_PROTO3; + case "editions": + // EDITION_UNSTABLE is a sandbox for in-development features. Collapse + // it to maximumEdition so SupportedEdition and feature resolution do + // not leak the test-only edition to users. + if (proto.edition === EDITION_UNSTABLE) { + return maximumEdition; + } + if (proto.edition in featureDefaults) { + return proto.edition; + } + throw new Error(`${proto.name}: unsupported edition`); + default: + throw new Error(`${proto.name}: unsupported syntax "${proto.syntax}"`); + } +} +/** + * Resolve dependencies of FileDescriptorProto to DescFile. + */ +function findFileDependencies(proto, reg) { + return proto.dependency.map((wantName) => { + const dep = reg.getFile(wantName); + if (!dep) { + throw new Error(`Cannot find ${wantName}, imported by ${proto.name}`); + } + return dep; + }); +} +/** + * Finds a prefix shared by enum values, for example `my_enum_` for + * `enum MyEnum {MY_ENUM_A=0; MY_ENUM_B=1;}`. + */ +function findEnumSharedPrefix(enumName, values) { + const prefix = camelToSnakeCase(enumName) + "_"; + for (const value of values) { + if (!value.name.toLowerCase().startsWith(prefix)) { + return undefined; + } + const shortName = value.name.substring(prefix.length); + if (shortName.length == 0) { + return undefined; + } + if (/^\d/.test(shortName)) { + // identifiers must not start with numbers + return undefined; + } + } + return prefix; +} +/** + * Converts lowerCamelCase or UpperCamelCase into lower_snake_case. + * This is used to find shared prefixes in an enum. + */ +function camelToSnakeCase(camel) { + return (camel.substring(0, 1) + camel.substring(1).replace(/[A-Z]/g, (c) => "_" + c)).toLowerCase(); +} +/** + * Create a fully qualified name for a protobuf type or extension field. + * + * The fully qualified name for messages, enumerations, and services is + * constructed by concatenating the package name (if present), parent + * message names (for nested types), and the type name. We omit the leading + * dot added by protobuf compilers. Examples: + * - mypackage.MyMessage + * - mypackage.MyMessage.NestedMessage + * + * The fully qualified name for extension fields is constructed by + * concatenating the package name (if present), parent message names (for + * extensions declared within a message), and the field name. Examples: + * - mypackage.extfield + * - mypackage.MyMessage.extfield + */ +function makeTypeName(proto, parent, file) { + let typeName; + if (parent) { + typeName = `${parent.typeName}.${proto.name}`; + } else if (file.proto.package.length > 0) { + typeName = `${file.proto.package}.${proto.name}`; + } else { + typeName = `${proto.name}`; + } + return typeName; +} +/** + * Remove the leading dot from a fully qualified type name. + */ +function trimLeadingDot(typeName) { + return typeName.startsWith(".") ? typeName.substring(1) : typeName; +} +/** + * Did the user put the field in a oneof group? + * Synthetic oneofs for proto3 optionals are ignored. + */ +function findOneof(proto, allOneofs) { + if (!unsafeIsSetExplicit(proto, "oneofIndex")) { + return undefined; + } + if (proto.proto3Optional) { + return undefined; + } + const oneof = allOneofs[proto.oneofIndex]; + assert$1(oneof, `invalid FieldDescriptorProto: oneof #${proto.oneofIndex} for field #${proto.number} not found`); + return oneof; +} +/** + * Presence of the field. + * See https://protobuf.dev/programming-guides/field_presence/ + */ +function getFieldPresence(proto, oneof, isExtension, parent) { + if (proto.label == LABEL_REQUIRED) { + // proto2 required is LEGACY_REQUIRED + return LEGACY_REQUIRED; + } + if (proto.label == LABEL_REPEATED) { + // repeated fields (including maps) do not track presence + return IMPLICIT; + } + if (!!oneof || proto.proto3Optional) { + // oneof is always explicit + return EXPLICIT; + } + if (isExtension) { + // extensions always track presence + return EXPLICIT; + } + const resolved = resolveFeature("fieldPresence", { proto, parent }); + if (resolved == IMPLICIT && (proto.type == TYPE_MESSAGE || proto.type == TYPE_GROUP)) { + // singular message field cannot be implicit + return EXPLICIT; + } + return resolved; +} +/** + * Pack this repeated field? + */ +function isPackedField(proto, parent) { + if (proto.label != LABEL_REPEATED) { + return false; + } + switch (proto.type) { + case TYPE_STRING: + case TYPE_BYTES: + case TYPE_GROUP: + case TYPE_MESSAGE: + // length-delimited types cannot be packed + return false; + } + const o = proto.options; + if (o && unsafeIsSetExplicit(o, "packed")) { + // prefer the field option over edition features + return o.packed; + } + return ( + PACKED == + resolveFeature("repeatedFieldEncoding", { + proto, + parent, + }) + ); +} +/** + * Find the key and value fields of a synthetic map entry message. + */ +function findMapEntryFields(mapEntry) { + const key = mapEntry.fields.find((f) => f.number === 1); + const value = mapEntry.fields.find((f) => f.number === 2); + assert$1( + key && + key.fieldKind == "scalar" && + key.scalar != ScalarType.BYTES && + key.scalar != ScalarType.FLOAT && + key.scalar != ScalarType.DOUBLE && + value && + value.fieldKind != "list" && + value.fieldKind != "map", + ); + return { key, value }; +} +/** + * Enumerations can be open or closed. + * See https://protobuf.dev/programming-guides/enum/ + */ +function isEnumOpen(desc) { + var _a; + return ( + OPEN == + resolveFeature("enumType", { + proto: desc.proto, + parent: (_a = desc.parent) !== null && _a !== void 0 ? _a : desc.file, + }) + ); +} +/** + * Encode the message delimited (a.k.a. proto2 group encoding), or + * length-prefixed? + */ +function isDelimitedEncoding(proto, parent) { + if (proto.type == TYPE_GROUP) { + return true; + } + return ( + DELIMITED == + resolveFeature("messageEncoding", { + proto, + parent, + }) + ); +} +/** + * Reject invalid UTF-8 when reading string fields from the binary wire format? + * Driven by the resolved `utf8_validation` feature: VERIFY (proto3 / editions + * 2023+ default) enforces; NONE (proto2 default) does not. + */ +function isUtf8Validated(proto, parent) { + return ( + VERIFY == + resolveFeature("utf8Validation", { + proto, + parent, + }) + ); +} +function resolveFeature(name, ref) { + var _a, _b; + const featureSet = (_a = ref.proto.options) === null || _a === void 0 ? void 0 : _a.features; + if (featureSet) { + const val = featureSet[name]; + if (val != 0) { + return val; + } + } + if ("kind" in ref) { + if (ref.kind == "message") { + return resolveFeature(name, (_b = ref.parent) !== null && _b !== void 0 ? _b : ref.file); + } + const editionDefaults = featureDefaults[ref.edition]; + if (!editionDefaults) { + throw new Error(`feature default for edition ${ref.edition} not found`); + } + return editionDefaults[name]; + } + return resolveFeature(name, ref.parent); +} +/** + * Assert that condition is truthy or throw error (with message) + */ +function assert$1(condition, msg) { + if (!condition) { + throw new Error(msg); + } +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Hydrate a file descriptor for google/protobuf/descriptor.proto from a plain + * object. + * + * See createFileDescriptorProtoBoot() for details. + * + * @private + */ +function boot(boot) { + const root = bootFileDescriptorProto(boot); + root.messageType.forEach(restoreJsonNames); + const reg = createFileRegistry(root, () => undefined); + // biome-ignore lint/style/noNonNullAssertion: non-null assertion because we just created the registry from the file we look up + return reg.getFile(root.name); +} +/** + * Creates the message google.protobuf.FileDescriptorProto from an object literal. + * + * See createFileDescriptorProtoBoot() for details. + * + * @private + */ +function bootFileDescriptorProto(init) { + const proto = Object.create({ + syntax: "", + edition: 0, + }); + return Object.assign( + proto, + Object.assign( + Object.assign({ $typeName: "google.protobuf.FileDescriptorProto", dependency: [], publicDependency: [], weakDependency: [], optionDependency: [], service: [], extension: [] }, init), + { messageType: init.messageType.map(bootDescriptorProto), enumType: init.enumType.map(bootEnumDescriptorProto) }, + ), + ); +} +function bootDescriptorProto(init) { + var _a, _b, _c, _d, _e, _f, _g, _h; + const proto = Object.create({ + visibility: 0, + }); + return Object.assign(proto, { + $typeName: "google.protobuf.DescriptorProto", + name: init.name, + field: (_b = (_a = init.field) === null || _a === void 0 ? void 0 : _a.map(bootFieldDescriptorProto)) !== null && _b !== void 0 ? _b : [], + extension: [], + nestedType: (_d = (_c = init.nestedType) === null || _c === void 0 ? void 0 : _c.map(bootDescriptorProto)) !== null && _d !== void 0 ? _d : [], + enumType: (_f = (_e = init.enumType) === null || _e === void 0 ? void 0 : _e.map(bootEnumDescriptorProto)) !== null && _f !== void 0 ? _f : [], + extensionRange: + ( + (_h = (_g = init.extensionRange) === null || _g === void 0 ? void 0 : _g.map((e) => Object.assign({ $typeName: "google.protobuf.DescriptorProto.ExtensionRange" }, e))) !== null && + _h !== void 0 + ) ? + _h + : [], + oneofDecl: [], + reservedRange: [], + reservedName: [], + }); +} +function bootFieldDescriptorProto(init) { + const proto = Object.create({ + label: 1, + typeName: "", + extendee: "", + defaultValue: "", + oneofIndex: 0, + jsonName: "", + proto3Optional: false, + }); + return Object.assign(proto, Object.assign(Object.assign({ $typeName: "google.protobuf.FieldDescriptorProto" }, init), { options: init.options ? bootFieldOptions(init.options) : undefined })); +} +function bootFieldOptions(init) { + var _a, _b, _c; + const proto = Object.create({ + ctype: 0, + packed: false, + jstype: 0, + lazy: false, + unverifiedLazy: false, + deprecated: false, + weak: false, + debugRedact: false, + retention: 0, + }); + return Object.assign( + proto, + Object.assign(Object.assign({ $typeName: "google.protobuf.FieldOptions" }, init), { + targets: (_a = init.targets) !== null && _a !== void 0 ? _a : [], + editionDefaults: + ( + (_c = (_b = init.editionDefaults) === null || _b === void 0 ? void 0 : _b.map((e) => Object.assign({ $typeName: "google.protobuf.FieldOptions.EditionDefault" }, e))) !== null && + _c !== void 0 + ) ? + _c + : [], + uninterpretedOption: [], + }), + ); +} +function bootEnumDescriptorProto(init) { + const proto = Object.create({ + visibility: 0, + }); + return Object.assign(proto, { + $typeName: "google.protobuf.EnumDescriptorProto", + name: init.name, + reservedName: [], + reservedRange: [], + value: init.value.map((e) => Object.assign({ $typeName: "google.protobuf.EnumValueDescriptorProto" }, e)), + }); +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Hydrate a message descriptor. + * + * @private + */ +function messageDesc(file, path, ...paths) { + return paths.reduce((acc, cur) => acc.nestedMessages[cur], file.messages[path]); +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Describes the file google/protobuf/descriptor.proto. + */ +const file_google_protobuf_descriptor = /*@__PURE__*/ boot({ + name: "google/protobuf/descriptor.proto", + package: "google.protobuf", + messageType: [ + { + name: "FileDescriptorSet", + field: [{ name: "file", number: 1, type: 11, label: 3, typeName: ".google.protobuf.FileDescriptorProto" }], + extensionRange: [{ start: 536000000, end: 536000001 }], + }, + { + name: "FileDescriptorProto", + field: [ + { name: "name", number: 1, type: 9, label: 1 }, + { name: "package", number: 2, type: 9, label: 1 }, + { name: "dependency", number: 3, type: 9, label: 3 }, + { name: "public_dependency", number: 10, type: 5, label: 3 }, + { name: "weak_dependency", number: 11, type: 5, label: 3 }, + { name: "option_dependency", number: 15, type: 9, label: 3 }, + { name: "message_type", number: 4, type: 11, label: 3, typeName: ".google.protobuf.DescriptorProto" }, + { name: "enum_type", number: 5, type: 11, label: 3, typeName: ".google.protobuf.EnumDescriptorProto" }, + { name: "service", number: 6, type: 11, label: 3, typeName: ".google.protobuf.ServiceDescriptorProto" }, + { name: "extension", number: 7, type: 11, label: 3, typeName: ".google.protobuf.FieldDescriptorProto" }, + { name: "options", number: 8, type: 11, label: 1, typeName: ".google.protobuf.FileOptions" }, + { name: "source_code_info", number: 9, type: 11, label: 1, typeName: ".google.protobuf.SourceCodeInfo" }, + { name: "syntax", number: 12, type: 9, label: 1 }, + { name: "edition", number: 14, type: 14, label: 1, typeName: ".google.protobuf.Edition" }, + ], + }, + { + name: "DescriptorProto", + field: [ + { name: "name", number: 1, type: 9, label: 1 }, + { name: "field", number: 2, type: 11, label: 3, typeName: ".google.protobuf.FieldDescriptorProto" }, + { name: "extension", number: 6, type: 11, label: 3, typeName: ".google.protobuf.FieldDescriptorProto" }, + { name: "nested_type", number: 3, type: 11, label: 3, typeName: ".google.protobuf.DescriptorProto" }, + { name: "enum_type", number: 4, type: 11, label: 3, typeName: ".google.protobuf.EnumDescriptorProto" }, + { name: "extension_range", number: 5, type: 11, label: 3, typeName: ".google.protobuf.DescriptorProto.ExtensionRange" }, + { name: "oneof_decl", number: 8, type: 11, label: 3, typeName: ".google.protobuf.OneofDescriptorProto" }, + { name: "options", number: 7, type: 11, label: 1, typeName: ".google.protobuf.MessageOptions" }, + { name: "reserved_range", number: 9, type: 11, label: 3, typeName: ".google.protobuf.DescriptorProto.ReservedRange" }, + { name: "reserved_name", number: 10, type: 9, label: 3 }, + { name: "visibility", number: 11, type: 14, label: 1, typeName: ".google.protobuf.SymbolVisibility" }, + ], + nestedType: [ + { + name: "ExtensionRange", + field: [ + { name: "start", number: 1, type: 5, label: 1 }, + { name: "end", number: 2, type: 5, label: 1 }, + { name: "options", number: 3, type: 11, label: 1, typeName: ".google.protobuf.ExtensionRangeOptions" }, + ], + }, + { + name: "ReservedRange", + field: [ + { name: "start", number: 1, type: 5, label: 1 }, + { name: "end", number: 2, type: 5, label: 1 }, + ], + }, + ], + }, + { + name: "ExtensionRangeOptions", + field: [ + { name: "uninterpreted_option", number: 999, type: 11, label: 3, typeName: ".google.protobuf.UninterpretedOption" }, + { name: "declaration", number: 2, type: 11, label: 3, typeName: ".google.protobuf.ExtensionRangeOptions.Declaration", options: { retention: 2 } }, + { name: "features", number: 50, type: 11, label: 1, typeName: ".google.protobuf.FeatureSet" }, + { name: "verification", number: 3, type: 14, label: 1, typeName: ".google.protobuf.ExtensionRangeOptions.VerificationState", defaultValue: "UNVERIFIED", options: { retention: 2 } }, + ], + nestedType: [ + { + name: "Declaration", + field: [ + { name: "number", number: 1, type: 5, label: 1 }, + { name: "full_name", number: 2, type: 9, label: 1 }, + { name: "type", number: 3, type: 9, label: 1 }, + { name: "reserved", number: 5, type: 8, label: 1 }, + { name: "repeated", number: 6, type: 8, label: 1 }, + ], + }, + ], + enumType: [ + { + name: "VerificationState", + value: [ + { name: "DECLARATION", number: 0 }, + { name: "UNVERIFIED", number: 1 }, + ], + }, + ], + extensionRange: [{ start: 1000, end: 536870912 }], + }, + { + name: "FieldDescriptorProto", + field: [ + { name: "name", number: 1, type: 9, label: 1 }, + { name: "number", number: 3, type: 5, label: 1 }, + { name: "label", number: 4, type: 14, label: 1, typeName: ".google.protobuf.FieldDescriptorProto.Label" }, + { name: "type", number: 5, type: 14, label: 1, typeName: ".google.protobuf.FieldDescriptorProto.Type" }, + { name: "type_name", number: 6, type: 9, label: 1 }, + { name: "extendee", number: 2, type: 9, label: 1 }, + { name: "default_value", number: 7, type: 9, label: 1 }, + { name: "oneof_index", number: 9, type: 5, label: 1 }, + { name: "json_name", number: 10, type: 9, label: 1 }, + { name: "options", number: 8, type: 11, label: 1, typeName: ".google.protobuf.FieldOptions" }, + { name: "proto3_optional", number: 17, type: 8, label: 1 }, + ], + enumType: [ + { + name: "Type", + value: [ + { name: "TYPE_DOUBLE", number: 1 }, + { name: "TYPE_FLOAT", number: 2 }, + { name: "TYPE_INT64", number: 3 }, + { name: "TYPE_UINT64", number: 4 }, + { name: "TYPE_INT32", number: 5 }, + { name: "TYPE_FIXED64", number: 6 }, + { name: "TYPE_FIXED32", number: 7 }, + { name: "TYPE_BOOL", number: 8 }, + { name: "TYPE_STRING", number: 9 }, + { name: "TYPE_GROUP", number: 10 }, + { name: "TYPE_MESSAGE", number: 11 }, + { name: "TYPE_BYTES", number: 12 }, + { name: "TYPE_UINT32", number: 13 }, + { name: "TYPE_ENUM", number: 14 }, + { name: "TYPE_SFIXED32", number: 15 }, + { name: "TYPE_SFIXED64", number: 16 }, + { name: "TYPE_SINT32", number: 17 }, + { name: "TYPE_SINT64", number: 18 }, + ], + }, + { + name: "Label", + value: [ + { name: "LABEL_OPTIONAL", number: 1 }, + { name: "LABEL_REPEATED", number: 3 }, + { name: "LABEL_REQUIRED", number: 2 }, + ], + }, + ], + }, + { + name: "OneofDescriptorProto", + field: [ + { name: "name", number: 1, type: 9, label: 1 }, + { name: "options", number: 2, type: 11, label: 1, typeName: ".google.protobuf.OneofOptions" }, + ], + }, + { + name: "EnumDescriptorProto", + field: [ + { name: "name", number: 1, type: 9, label: 1 }, + { name: "value", number: 2, type: 11, label: 3, typeName: ".google.protobuf.EnumValueDescriptorProto" }, + { name: "options", number: 3, type: 11, label: 1, typeName: ".google.protobuf.EnumOptions" }, + { name: "reserved_range", number: 4, type: 11, label: 3, typeName: ".google.protobuf.EnumDescriptorProto.EnumReservedRange" }, + { name: "reserved_name", number: 5, type: 9, label: 3 }, + { name: "visibility", number: 6, type: 14, label: 1, typeName: ".google.protobuf.SymbolVisibility" }, + ], + nestedType: [ + { + name: "EnumReservedRange", + field: [ + { name: "start", number: 1, type: 5, label: 1 }, + { name: "end", number: 2, type: 5, label: 1 }, + ], + }, + ], + }, + { + name: "EnumValueDescriptorProto", + field: [ + { name: "name", number: 1, type: 9, label: 1 }, + { name: "number", number: 2, type: 5, label: 1 }, + { name: "options", number: 3, type: 11, label: 1, typeName: ".google.protobuf.EnumValueOptions" }, + ], + }, + { + name: "ServiceDescriptorProto", + field: [ + { name: "name", number: 1, type: 9, label: 1 }, + { name: "method", number: 2, type: 11, label: 3, typeName: ".google.protobuf.MethodDescriptorProto" }, + { name: "options", number: 3, type: 11, label: 1, typeName: ".google.protobuf.ServiceOptions" }, + ], + }, + { + name: "MethodDescriptorProto", + field: [ + { name: "name", number: 1, type: 9, label: 1 }, + { name: "input_type", number: 2, type: 9, label: 1 }, + { name: "output_type", number: 3, type: 9, label: 1 }, + { name: "options", number: 4, type: 11, label: 1, typeName: ".google.protobuf.MethodOptions" }, + { name: "client_streaming", number: 5, type: 8, label: 1, defaultValue: "false" }, + { name: "server_streaming", number: 6, type: 8, label: 1, defaultValue: "false" }, + ], + }, + { + name: "FileOptions", + field: [ + { name: "java_package", number: 1, type: 9, label: 1 }, + { name: "java_outer_classname", number: 8, type: 9, label: 1 }, + { name: "java_multiple_files", number: 10, type: 8, label: 1, defaultValue: "false", options: {} }, + { name: "java_generate_equals_and_hash", number: 20, type: 8, label: 1, options: { deprecated: true } }, + { name: "java_string_check_utf8", number: 27, type: 8, label: 1, defaultValue: "false" }, + { name: "optimize_for", number: 9, type: 14, label: 1, typeName: ".google.protobuf.FileOptions.OptimizeMode", defaultValue: "SPEED" }, + { name: "go_package", number: 11, type: 9, label: 1 }, + { name: "cc_generic_services", number: 16, type: 8, label: 1, defaultValue: "false" }, + { name: "java_generic_services", number: 17, type: 8, label: 1, defaultValue: "false" }, + { name: "py_generic_services", number: 18, type: 8, label: 1, defaultValue: "false" }, + { name: "deprecated", number: 23, type: 8, label: 1, defaultValue: "false" }, + { name: "cc_enable_arenas", number: 31, type: 8, label: 1, defaultValue: "true" }, + { name: "objc_class_prefix", number: 36, type: 9, label: 1 }, + { name: "csharp_namespace", number: 37, type: 9, label: 1 }, + { name: "swift_prefix", number: 39, type: 9, label: 1 }, + { name: "php_class_prefix", number: 40, type: 9, label: 1 }, + { name: "php_namespace", number: 41, type: 9, label: 1 }, + { name: "php_metadata_namespace", number: 44, type: 9, label: 1 }, + { name: "ruby_package", number: 45, type: 9, label: 1 }, + { name: "features", number: 50, type: 11, label: 1, typeName: ".google.protobuf.FeatureSet" }, + { name: "uninterpreted_option", number: 999, type: 11, label: 3, typeName: ".google.protobuf.UninterpretedOption" }, + ], + enumType: [ + { + name: "OptimizeMode", + value: [ + { name: "SPEED", number: 1 }, + { name: "CODE_SIZE", number: 2 }, + { name: "LITE_RUNTIME", number: 3 }, + ], + }, + ], + extensionRange: [{ start: 1000, end: 536870912 }], + }, + { + name: "MessageOptions", + field: [ + { name: "message_set_wire_format", number: 1, type: 8, label: 1, defaultValue: "false" }, + { name: "no_standard_descriptor_accessor", number: 2, type: 8, label: 1, defaultValue: "false" }, + { name: "deprecated", number: 3, type: 8, label: 1, defaultValue: "false" }, + { name: "map_entry", number: 7, type: 8, label: 1 }, + { name: "deprecated_legacy_json_field_conflicts", number: 11, type: 8, label: 1, options: { deprecated: true } }, + { name: "features", number: 12, type: 11, label: 1, typeName: ".google.protobuf.FeatureSet" }, + { name: "uninterpreted_option", number: 999, type: 11, label: 3, typeName: ".google.protobuf.UninterpretedOption" }, + ], + extensionRange: [{ start: 1000, end: 536870912 }], + }, + { + name: "FieldOptions", + field: [ + { name: "ctype", number: 1, type: 14, label: 1, typeName: ".google.protobuf.FieldOptions.CType", defaultValue: "STRING" }, + { name: "packed", number: 2, type: 8, label: 1 }, + { name: "jstype", number: 6, type: 14, label: 1, typeName: ".google.protobuf.FieldOptions.JSType", defaultValue: "JS_NORMAL" }, + { name: "lazy", number: 5, type: 8, label: 1, defaultValue: "false" }, + { name: "unverified_lazy", number: 15, type: 8, label: 1, defaultValue: "false" }, + { name: "deprecated", number: 3, type: 8, label: 1, defaultValue: "false" }, + { name: "weak", number: 10, type: 8, label: 1, defaultValue: "false", options: { deprecated: true } }, + { name: "debug_redact", number: 16, type: 8, label: 1, defaultValue: "false" }, + { name: "retention", number: 17, type: 14, label: 1, typeName: ".google.protobuf.FieldOptions.OptionRetention" }, + { name: "targets", number: 19, type: 14, label: 3, typeName: ".google.protobuf.FieldOptions.OptionTargetType" }, + { name: "edition_defaults", number: 20, type: 11, label: 3, typeName: ".google.protobuf.FieldOptions.EditionDefault" }, + { name: "features", number: 21, type: 11, label: 1, typeName: ".google.protobuf.FeatureSet" }, + { name: "feature_support", number: 22, type: 11, label: 1, typeName: ".google.protobuf.FieldOptions.FeatureSupport" }, + { name: "uninterpreted_option", number: 999, type: 11, label: 3, typeName: ".google.protobuf.UninterpretedOption" }, + ], + nestedType: [ + { + name: "EditionDefault", + field: [ + { name: "edition", number: 3, type: 14, label: 1, typeName: ".google.protobuf.Edition" }, + { name: "value", number: 2, type: 9, label: 1 }, + ], + }, + { + name: "FeatureSupport", + field: [ + { name: "edition_introduced", number: 1, type: 14, label: 1, typeName: ".google.protobuf.Edition" }, + { name: "edition_deprecated", number: 2, type: 14, label: 1, typeName: ".google.protobuf.Edition" }, + { name: "deprecation_warning", number: 3, type: 9, label: 1 }, + { name: "edition_removed", number: 4, type: 14, label: 1, typeName: ".google.protobuf.Edition" }, + { name: "removal_error", number: 5, type: 9, label: 1 }, + ], + }, + ], + enumType: [ + { + name: "CType", + value: [ + { name: "STRING", number: 0 }, + { name: "CORD", number: 1 }, + { name: "STRING_PIECE", number: 2 }, + ], + }, + { + name: "JSType", + value: [ + { name: "JS_NORMAL", number: 0 }, + { name: "JS_STRING", number: 1 }, + { name: "JS_NUMBER", number: 2 }, + ], + }, + { + name: "OptionRetention", + value: [ + { name: "RETENTION_UNKNOWN", number: 0 }, + { name: "RETENTION_RUNTIME", number: 1 }, + { name: "RETENTION_SOURCE", number: 2 }, + ], + }, + { + name: "OptionTargetType", + value: [ + { name: "TARGET_TYPE_UNKNOWN", number: 0 }, + { name: "TARGET_TYPE_FILE", number: 1 }, + { name: "TARGET_TYPE_EXTENSION_RANGE", number: 2 }, + { name: "TARGET_TYPE_MESSAGE", number: 3 }, + { name: "TARGET_TYPE_FIELD", number: 4 }, + { name: "TARGET_TYPE_ONEOF", number: 5 }, + { name: "TARGET_TYPE_ENUM", number: 6 }, + { name: "TARGET_TYPE_ENUM_ENTRY", number: 7 }, + { name: "TARGET_TYPE_SERVICE", number: 8 }, + { name: "TARGET_TYPE_METHOD", number: 9 }, + ], + }, + ], + extensionRange: [{ start: 1000, end: 536870912 }], + }, + { + name: "OneofOptions", + field: [ + { name: "features", number: 1, type: 11, label: 1, typeName: ".google.protobuf.FeatureSet" }, + { name: "uninterpreted_option", number: 999, type: 11, label: 3, typeName: ".google.protobuf.UninterpretedOption" }, + ], + extensionRange: [{ start: 1000, end: 536870912 }], + }, + { + name: "EnumOptions", + field: [ + { name: "allow_alias", number: 2, type: 8, label: 1 }, + { name: "deprecated", number: 3, type: 8, label: 1, defaultValue: "false" }, + { name: "deprecated_legacy_json_field_conflicts", number: 6, type: 8, label: 1, options: { deprecated: true } }, + { name: "features", number: 7, type: 11, label: 1, typeName: ".google.protobuf.FeatureSet" }, + { name: "uninterpreted_option", number: 999, type: 11, label: 3, typeName: ".google.protobuf.UninterpretedOption" }, + ], + extensionRange: [{ start: 1000, end: 536870912 }], + }, + { + name: "EnumValueOptions", + field: [ + { name: "deprecated", number: 1, type: 8, label: 1, defaultValue: "false" }, + { name: "features", number: 2, type: 11, label: 1, typeName: ".google.protobuf.FeatureSet" }, + { name: "debug_redact", number: 3, type: 8, label: 1, defaultValue: "false" }, + { name: "feature_support", number: 4, type: 11, label: 1, typeName: ".google.protobuf.FieldOptions.FeatureSupport" }, + { name: "uninterpreted_option", number: 999, type: 11, label: 3, typeName: ".google.protobuf.UninterpretedOption" }, + ], + extensionRange: [{ start: 1000, end: 536870912 }], + }, + { + name: "ServiceOptions", + field: [ + { name: "features", number: 34, type: 11, label: 1, typeName: ".google.protobuf.FeatureSet" }, + { name: "deprecated", number: 33, type: 8, label: 1, defaultValue: "false" }, + { name: "uninterpreted_option", number: 999, type: 11, label: 3, typeName: ".google.protobuf.UninterpretedOption" }, + ], + extensionRange: [{ start: 1000, end: 536870912 }], + }, + { + name: "MethodOptions", + field: [ + { name: "deprecated", number: 33, type: 8, label: 1, defaultValue: "false" }, + { name: "idempotency_level", number: 34, type: 14, label: 1, typeName: ".google.protobuf.MethodOptions.IdempotencyLevel", defaultValue: "IDEMPOTENCY_UNKNOWN" }, + { name: "features", number: 35, type: 11, label: 1, typeName: ".google.protobuf.FeatureSet" }, + { name: "uninterpreted_option", number: 999, type: 11, label: 3, typeName: ".google.protobuf.UninterpretedOption" }, + ], + enumType: [ + { + name: "IdempotencyLevel", + value: [ + { name: "IDEMPOTENCY_UNKNOWN", number: 0 }, + { name: "NO_SIDE_EFFECTS", number: 1 }, + { name: "IDEMPOTENT", number: 2 }, + ], + }, + ], + extensionRange: [{ start: 1000, end: 536870912 }], + }, + { + name: "UninterpretedOption", + field: [ + { name: "name", number: 2, type: 11, label: 3, typeName: ".google.protobuf.UninterpretedOption.NamePart" }, + { name: "identifier_value", number: 3, type: 9, label: 1 }, + { name: "positive_int_value", number: 4, type: 4, label: 1 }, + { name: "negative_int_value", number: 5, type: 3, label: 1 }, + { name: "double_value", number: 6, type: 1, label: 1 }, + { name: "string_value", number: 7, type: 12, label: 1 }, + { name: "aggregate_value", number: 8, type: 9, label: 1 }, + ], + nestedType: [ + { + name: "NamePart", + field: [ + { name: "name_part", number: 1, type: 9, label: 2 }, + { name: "is_extension", number: 2, type: 8, label: 2 }, + ], + }, + ], + }, + { + name: "FeatureSet", + field: [ + { + name: "field_presence", + number: 1, + type: 14, + label: 1, + typeName: ".google.protobuf.FeatureSet.FieldPresence", + options: { + retention: 1, + targets: [4, 1], + editionDefaults: [ + { value: "EXPLICIT", edition: 900 }, + { value: "IMPLICIT", edition: 999 }, + { value: "EXPLICIT", edition: 1000 }, + ], + }, + }, + { + name: "enum_type", + number: 2, + type: 14, + label: 1, + typeName: ".google.protobuf.FeatureSet.EnumType", + options: { + retention: 1, + targets: [6, 1], + editionDefaults: [ + { value: "CLOSED", edition: 900 }, + { value: "OPEN", edition: 999 }, + ], + }, + }, + { + name: "repeated_field_encoding", + number: 3, + type: 14, + label: 1, + typeName: ".google.protobuf.FeatureSet.RepeatedFieldEncoding", + options: { + retention: 1, + targets: [4, 1], + editionDefaults: [ + { value: "EXPANDED", edition: 900 }, + { value: "PACKED", edition: 999 }, + ], + }, + }, + { + name: "utf8_validation", + number: 4, + type: 14, + label: 1, + typeName: ".google.protobuf.FeatureSet.Utf8Validation", + options: { + retention: 1, + targets: [4, 1], + editionDefaults: [ + { value: "NONE", edition: 900 }, + { value: "VERIFY", edition: 999 }, + ], + }, + }, + { + name: "message_encoding", + number: 5, + type: 14, + label: 1, + typeName: ".google.protobuf.FeatureSet.MessageEncoding", + options: { retention: 1, targets: [4, 1], editionDefaults: [{ value: "LENGTH_PREFIXED", edition: 900 }] }, + }, + { + name: "json_format", + number: 6, + type: 14, + label: 1, + typeName: ".google.protobuf.FeatureSet.JsonFormat", + options: { + retention: 1, + targets: [3, 6, 1], + editionDefaults: [ + { value: "LEGACY_BEST_EFFORT", edition: 900 }, + { value: "ALLOW", edition: 999 }, + ], + }, + }, + { + name: "enforce_naming_style", + number: 7, + type: 14, + label: 1, + typeName: ".google.protobuf.FeatureSet.EnforceNamingStyle", + options: { + retention: 2, + targets: [1, 2, 3, 4, 5, 6, 7, 8, 9], + editionDefaults: [ + { value: "STYLE_LEGACY", edition: 900 }, + { value: "STYLE2024", edition: 1001 }, + ], + }, + }, + { + name: "default_symbol_visibility", + number: 8, + type: 14, + label: 1, + typeName: ".google.protobuf.FeatureSet.VisibilityFeature.DefaultSymbolVisibility", + options: { + retention: 2, + targets: [1], + editionDefaults: [ + { value: "EXPORT_ALL", edition: 900 }, + { value: "EXPORT_TOP_LEVEL", edition: 1001 }, + ], + }, + }, + ], + nestedType: [ + { + name: "VisibilityFeature", + enumType: [ + { + name: "DefaultSymbolVisibility", + value: [ + { name: "DEFAULT_SYMBOL_VISIBILITY_UNKNOWN", number: 0 }, + { name: "EXPORT_ALL", number: 1 }, + { name: "EXPORT_TOP_LEVEL", number: 2 }, + { name: "LOCAL_ALL", number: 3 }, + { name: "STRICT", number: 4 }, + ], + }, + ], + }, + ], + enumType: [ + { + name: "FieldPresence", + value: [ + { name: "FIELD_PRESENCE_UNKNOWN", number: 0 }, + { name: "EXPLICIT", number: 1 }, + { name: "IMPLICIT", number: 2 }, + { name: "LEGACY_REQUIRED", number: 3 }, + ], + }, + { + name: "EnumType", + value: [ + { name: "ENUM_TYPE_UNKNOWN", number: 0 }, + { name: "OPEN", number: 1 }, + { name: "CLOSED", number: 2 }, + ], + }, + { + name: "RepeatedFieldEncoding", + value: [ + { name: "REPEATED_FIELD_ENCODING_UNKNOWN", number: 0 }, + { name: "PACKED", number: 1 }, + { name: "EXPANDED", number: 2 }, + ], + }, + { + name: "Utf8Validation", + value: [ + { name: "UTF8_VALIDATION_UNKNOWN", number: 0 }, + { name: "VERIFY", number: 2 }, + { name: "NONE", number: 3 }, + ], + }, + { + name: "MessageEncoding", + value: [ + { name: "MESSAGE_ENCODING_UNKNOWN", number: 0 }, + { name: "LENGTH_PREFIXED", number: 1 }, + { name: "DELIMITED", number: 2 }, + ], + }, + { + name: "JsonFormat", + value: [ + { name: "JSON_FORMAT_UNKNOWN", number: 0 }, + { name: "ALLOW", number: 1 }, + { name: "LEGACY_BEST_EFFORT", number: 2 }, + ], + }, + { + name: "EnforceNamingStyle", + value: [ + { name: "ENFORCE_NAMING_STYLE_UNKNOWN", number: 0 }, + { name: "STYLE2024", number: 1 }, + { name: "STYLE_LEGACY", number: 2 }, + ], + }, + ], + extensionRange: [ + { start: 1000, end: 9995 }, + { start: 9995, end: 10000 }, + { start: 10000, end: 10001 }, + ], + }, + { + name: "FeatureSetDefaults", + field: [ + { name: "defaults", number: 1, type: 11, label: 3, typeName: ".google.protobuf.FeatureSetDefaults.FeatureSetEditionDefault" }, + { name: "minimum_edition", number: 4, type: 14, label: 1, typeName: ".google.protobuf.Edition" }, + { name: "maximum_edition", number: 5, type: 14, label: 1, typeName: ".google.protobuf.Edition" }, + ], + nestedType: [ + { + name: "FeatureSetEditionDefault", + field: [ + { name: "edition", number: 3, type: 14, label: 1, typeName: ".google.protobuf.Edition" }, + { name: "overridable_features", number: 4, type: 11, label: 1, typeName: ".google.protobuf.FeatureSet" }, + { name: "fixed_features", number: 5, type: 11, label: 1, typeName: ".google.protobuf.FeatureSet" }, + ], + }, + ], + }, + { + name: "SourceCodeInfo", + field: [{ name: "location", number: 1, type: 11, label: 3, typeName: ".google.protobuf.SourceCodeInfo.Location" }], + nestedType: [ + { + name: "Location", + field: [ + { name: "path", number: 1, type: 5, label: 3, options: { packed: true } }, + { name: "span", number: 2, type: 5, label: 3, options: { packed: true } }, + { name: "leading_comments", number: 3, type: 9, label: 1 }, + { name: "trailing_comments", number: 4, type: 9, label: 1 }, + { name: "leading_detached_comments", number: 6, type: 9, label: 3 }, + ], + }, + ], + extensionRange: [{ start: 536000000, end: 536000001 }], + }, + { + name: "GeneratedCodeInfo", + field: [{ name: "annotation", number: 1, type: 11, label: 3, typeName: ".google.protobuf.GeneratedCodeInfo.Annotation" }], + nestedType: [ + { + name: "Annotation", + field: [ + { name: "path", number: 1, type: 5, label: 3, options: { packed: true } }, + { name: "source_file", number: 2, type: 9, label: 1 }, + { name: "begin", number: 3, type: 5, label: 1 }, + { name: "end", number: 4, type: 5, label: 1 }, + { name: "semantic", number: 5, type: 14, label: 1, typeName: ".google.protobuf.GeneratedCodeInfo.Annotation.Semantic" }, + ], + enumType: [ + { + name: "Semantic", + value: [ + { name: "NONE", number: 0 }, + { name: "SET", number: 1 }, + { name: "ALIAS", number: 2 }, + ], + }, + ], + }, + ], + }, + ], + enumType: [ + { + name: "Edition", + value: [ + { name: "EDITION_UNKNOWN", number: 0 }, + { name: "EDITION_LEGACY", number: 900 }, + { name: "EDITION_PROTO2", number: 998 }, + { name: "EDITION_PROTO3", number: 999 }, + { name: "EDITION_2023", number: 1000 }, + { name: "EDITION_2024", number: 1001 }, + { name: "EDITION_UNSTABLE", number: 9999 }, + { name: "EDITION_1_TEST_ONLY", number: 1 }, + { name: "EDITION_2_TEST_ONLY", number: 2 }, + { name: "EDITION_99997_TEST_ONLY", number: 99997 }, + { name: "EDITION_99998_TEST_ONLY", number: 99998 }, + { name: "EDITION_99999_TEST_ONLY", number: 99999 }, + { name: "EDITION_MAX", number: 2147483647 }, + ], + }, + { + name: "SymbolVisibility", + value: [ + { name: "VISIBILITY_UNSET", number: 0 }, + { name: "VISIBILITY_LOCAL", number: 1 }, + { name: "VISIBILITY_EXPORT", number: 2 }, + ], + }, + ], +}); +/** + * Describes the message google.protobuf.FileDescriptorProto. + * Use `create(FileDescriptorProtoSchema)` to create a new message. + */ +const FileDescriptorProtoSchema = /*@__PURE__*/ messageDesc(file_google_protobuf_descriptor, 1); +/** + * The verification state of the extension range. + * + * @generated from enum google.protobuf.ExtensionRangeOptions.VerificationState + */ +var ExtensionRangeOptions_VerificationState; +(function (ExtensionRangeOptions_VerificationState) { + /** + * All the extensions of the range must be declared. + * + * @generated from enum value: DECLARATION = 0; + */ + ExtensionRangeOptions_VerificationState[(ExtensionRangeOptions_VerificationState["DECLARATION"] = 0)] = "DECLARATION"; + /** + * @generated from enum value: UNVERIFIED = 1; + */ + ExtensionRangeOptions_VerificationState[(ExtensionRangeOptions_VerificationState["UNVERIFIED"] = 1)] = "UNVERIFIED"; +})(ExtensionRangeOptions_VerificationState || (ExtensionRangeOptions_VerificationState = {})); +/** + * @generated from enum google.protobuf.FieldDescriptorProto.Type + */ +var FieldDescriptorProto_Type; +(function (FieldDescriptorProto_Type) { + /** + * 0 is reserved for errors. + * Order is weird for historical reasons. + * + * @generated from enum value: TYPE_DOUBLE = 1; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["DOUBLE"] = 1)] = "DOUBLE"; + /** + * @generated from enum value: TYPE_FLOAT = 2; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["FLOAT"] = 2)] = "FLOAT"; + /** + * Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + * negative values are likely. + * + * @generated from enum value: TYPE_INT64 = 3; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["INT64"] = 3)] = "INT64"; + /** + * @generated from enum value: TYPE_UINT64 = 4; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["UINT64"] = 4)] = "UINT64"; + /** + * Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + * negative values are likely. + * + * @generated from enum value: TYPE_INT32 = 5; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["INT32"] = 5)] = "INT32"; + /** + * @generated from enum value: TYPE_FIXED64 = 6; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["FIXED64"] = 6)] = "FIXED64"; + /** + * @generated from enum value: TYPE_FIXED32 = 7; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["FIXED32"] = 7)] = "FIXED32"; + /** + * @generated from enum value: TYPE_BOOL = 8; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["BOOL"] = 8)] = "BOOL"; + /** + * @generated from enum value: TYPE_STRING = 9; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["STRING"] = 9)] = "STRING"; + /** + * Tag-delimited aggregate. + * Group type is deprecated and not supported after google.protobuf. However, Proto3 + * implementations should still be able to parse the group wire format and + * treat group fields as unknown fields. In Editions, the group wire format + * can be enabled via the `message_encoding` feature. + * + * @generated from enum value: TYPE_GROUP = 10; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["GROUP"] = 10)] = "GROUP"; + /** + * Length-delimited aggregate. + * + * @generated from enum value: TYPE_MESSAGE = 11; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["MESSAGE"] = 11)] = "MESSAGE"; + /** + * New in version 2. + * + * @generated from enum value: TYPE_BYTES = 12; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["BYTES"] = 12)] = "BYTES"; + /** + * @generated from enum value: TYPE_UINT32 = 13; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["UINT32"] = 13)] = "UINT32"; + /** + * @generated from enum value: TYPE_ENUM = 14; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["ENUM"] = 14)] = "ENUM"; + /** + * @generated from enum value: TYPE_SFIXED32 = 15; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["SFIXED32"] = 15)] = "SFIXED32"; + /** + * @generated from enum value: TYPE_SFIXED64 = 16; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["SFIXED64"] = 16)] = "SFIXED64"; + /** + * Uses ZigZag encoding. + * + * @generated from enum value: TYPE_SINT32 = 17; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["SINT32"] = 17)] = "SINT32"; + /** + * Uses ZigZag encoding. + * + * @generated from enum value: TYPE_SINT64 = 18; + */ + FieldDescriptorProto_Type[(FieldDescriptorProto_Type["SINT64"] = 18)] = "SINT64"; +})(FieldDescriptorProto_Type || (FieldDescriptorProto_Type = {})); +/** + * @generated from enum google.protobuf.FieldDescriptorProto.Label + */ +var FieldDescriptorProto_Label; +(function (FieldDescriptorProto_Label) { + /** + * 0 is reserved for errors + * + * @generated from enum value: LABEL_OPTIONAL = 1; + */ + FieldDescriptorProto_Label[(FieldDescriptorProto_Label["OPTIONAL"] = 1)] = "OPTIONAL"; + /** + * @generated from enum value: LABEL_REPEATED = 3; + */ + FieldDescriptorProto_Label[(FieldDescriptorProto_Label["REPEATED"] = 3)] = "REPEATED"; + /** + * The required label is only allowed in google.protobuf. In proto3 and Editions + * it's explicitly prohibited. In Editions, the `field_presence` feature + * can be used to get this behavior. + * + * @generated from enum value: LABEL_REQUIRED = 2; + */ + FieldDescriptorProto_Label[(FieldDescriptorProto_Label["REQUIRED"] = 2)] = "REQUIRED"; +})(FieldDescriptorProto_Label || (FieldDescriptorProto_Label = {})); +/** + * Generated classes can be optimized for speed or code size. + * + * @generated from enum google.protobuf.FileOptions.OptimizeMode + */ +var FileOptions_OptimizeMode; +(function (FileOptions_OptimizeMode) { + /** + * Generate complete code for parsing, serialization, + * + * @generated from enum value: SPEED = 1; + */ + FileOptions_OptimizeMode[(FileOptions_OptimizeMode["SPEED"] = 1)] = "SPEED"; + /** + * etc. + * + * Use ReflectionOps to implement these methods. + * + * @generated from enum value: CODE_SIZE = 2; + */ + FileOptions_OptimizeMode[(FileOptions_OptimizeMode["CODE_SIZE"] = 2)] = "CODE_SIZE"; + /** + * Generate code using MessageLite and the lite runtime. + * + * @generated from enum value: LITE_RUNTIME = 3; + */ + FileOptions_OptimizeMode[(FileOptions_OptimizeMode["LITE_RUNTIME"] = 3)] = "LITE_RUNTIME"; +})(FileOptions_OptimizeMode || (FileOptions_OptimizeMode = {})); +/** + * @generated from enum google.protobuf.FieldOptions.CType + */ +var FieldOptions_CType; +(function (FieldOptions_CType) { + /** + * Default mode. + * + * @generated from enum value: STRING = 0; + */ + FieldOptions_CType[(FieldOptions_CType["STRING"] = 0)] = "STRING"; + /** + * The option [ctype=CORD] may be applied to a non-repeated field of type + * "bytes". It indicates that in C++, the data should be stored in a Cord + * instead of a string. For very large strings, this may reduce memory + * fragmentation. It may also allow better performance when parsing from a + * Cord, or when parsing with aliasing enabled, as the parsed Cord may then + * alias the original buffer. + * + * @generated from enum value: CORD = 1; + */ + FieldOptions_CType[(FieldOptions_CType["CORD"] = 1)] = "CORD"; + /** + * @generated from enum value: STRING_PIECE = 2; + */ + FieldOptions_CType[(FieldOptions_CType["STRING_PIECE"] = 2)] = "STRING_PIECE"; +})(FieldOptions_CType || (FieldOptions_CType = {})); +/** + * @generated from enum google.protobuf.FieldOptions.JSType + */ +var FieldOptions_JSType; +(function (FieldOptions_JSType) { + /** + * Use the default type. + * + * @generated from enum value: JS_NORMAL = 0; + */ + FieldOptions_JSType[(FieldOptions_JSType["JS_NORMAL"] = 0)] = "JS_NORMAL"; + /** + * Use JavaScript strings. + * + * @generated from enum value: JS_STRING = 1; + */ + FieldOptions_JSType[(FieldOptions_JSType["JS_STRING"] = 1)] = "JS_STRING"; + /** + * Use JavaScript numbers. + * + * @generated from enum value: JS_NUMBER = 2; + */ + FieldOptions_JSType[(FieldOptions_JSType["JS_NUMBER"] = 2)] = "JS_NUMBER"; +})(FieldOptions_JSType || (FieldOptions_JSType = {})); +/** + * If set to RETENTION_SOURCE, the option will be omitted from the binary. + * + * @generated from enum google.protobuf.FieldOptions.OptionRetention + */ +var FieldOptions_OptionRetention; +(function (FieldOptions_OptionRetention) { + /** + * @generated from enum value: RETENTION_UNKNOWN = 0; + */ + FieldOptions_OptionRetention[(FieldOptions_OptionRetention["RETENTION_UNKNOWN"] = 0)] = "RETENTION_UNKNOWN"; + /** + * @generated from enum value: RETENTION_RUNTIME = 1; + */ + FieldOptions_OptionRetention[(FieldOptions_OptionRetention["RETENTION_RUNTIME"] = 1)] = "RETENTION_RUNTIME"; + /** + * @generated from enum value: RETENTION_SOURCE = 2; + */ + FieldOptions_OptionRetention[(FieldOptions_OptionRetention["RETENTION_SOURCE"] = 2)] = "RETENTION_SOURCE"; +})(FieldOptions_OptionRetention || (FieldOptions_OptionRetention = {})); +/** + * This indicates the types of entities that the field may apply to when used + * as an option. If it is unset, then the field may be freely used as an + * option on any kind of entity. + * + * @generated from enum google.protobuf.FieldOptions.OptionTargetType + */ +var FieldOptions_OptionTargetType; +(function (FieldOptions_OptionTargetType) { + /** + * @generated from enum value: TARGET_TYPE_UNKNOWN = 0; + */ + FieldOptions_OptionTargetType[(FieldOptions_OptionTargetType["TARGET_TYPE_UNKNOWN"] = 0)] = "TARGET_TYPE_UNKNOWN"; + /** + * @generated from enum value: TARGET_TYPE_FILE = 1; + */ + FieldOptions_OptionTargetType[(FieldOptions_OptionTargetType["TARGET_TYPE_FILE"] = 1)] = "TARGET_TYPE_FILE"; + /** + * @generated from enum value: TARGET_TYPE_EXTENSION_RANGE = 2; + */ + FieldOptions_OptionTargetType[(FieldOptions_OptionTargetType["TARGET_TYPE_EXTENSION_RANGE"] = 2)] = "TARGET_TYPE_EXTENSION_RANGE"; + /** + * @generated from enum value: TARGET_TYPE_MESSAGE = 3; + */ + FieldOptions_OptionTargetType[(FieldOptions_OptionTargetType["TARGET_TYPE_MESSAGE"] = 3)] = "TARGET_TYPE_MESSAGE"; + /** + * @generated from enum value: TARGET_TYPE_FIELD = 4; + */ + FieldOptions_OptionTargetType[(FieldOptions_OptionTargetType["TARGET_TYPE_FIELD"] = 4)] = "TARGET_TYPE_FIELD"; + /** + * @generated from enum value: TARGET_TYPE_ONEOF = 5; + */ + FieldOptions_OptionTargetType[(FieldOptions_OptionTargetType["TARGET_TYPE_ONEOF"] = 5)] = "TARGET_TYPE_ONEOF"; + /** + * @generated from enum value: TARGET_TYPE_ENUM = 6; + */ + FieldOptions_OptionTargetType[(FieldOptions_OptionTargetType["TARGET_TYPE_ENUM"] = 6)] = "TARGET_TYPE_ENUM"; + /** + * @generated from enum value: TARGET_TYPE_ENUM_ENTRY = 7; + */ + FieldOptions_OptionTargetType[(FieldOptions_OptionTargetType["TARGET_TYPE_ENUM_ENTRY"] = 7)] = "TARGET_TYPE_ENUM_ENTRY"; + /** + * @generated from enum value: TARGET_TYPE_SERVICE = 8; + */ + FieldOptions_OptionTargetType[(FieldOptions_OptionTargetType["TARGET_TYPE_SERVICE"] = 8)] = "TARGET_TYPE_SERVICE"; + /** + * @generated from enum value: TARGET_TYPE_METHOD = 9; + */ + FieldOptions_OptionTargetType[(FieldOptions_OptionTargetType["TARGET_TYPE_METHOD"] = 9)] = "TARGET_TYPE_METHOD"; +})(FieldOptions_OptionTargetType || (FieldOptions_OptionTargetType = {})); +/** + * Is this method side-effect-free (or safe in HTTP parlance), or idempotent, + * or neither? HTTP based RPC implementation may choose GET verb for safe + * methods, and PUT verb for idempotent methods instead of the default POST. + * + * @generated from enum google.protobuf.MethodOptions.IdempotencyLevel + */ +var MethodOptions_IdempotencyLevel; +(function (MethodOptions_IdempotencyLevel) { + /** + * @generated from enum value: IDEMPOTENCY_UNKNOWN = 0; + */ + MethodOptions_IdempotencyLevel[(MethodOptions_IdempotencyLevel["IDEMPOTENCY_UNKNOWN"] = 0)] = "IDEMPOTENCY_UNKNOWN"; + /** + * implies idempotent + * + * @generated from enum value: NO_SIDE_EFFECTS = 1; + */ + MethodOptions_IdempotencyLevel[(MethodOptions_IdempotencyLevel["NO_SIDE_EFFECTS"] = 1)] = "NO_SIDE_EFFECTS"; + /** + * idempotent, but may have side effects + * + * @generated from enum value: IDEMPOTENT = 2; + */ + MethodOptions_IdempotencyLevel[(MethodOptions_IdempotencyLevel["IDEMPOTENT"] = 2)] = "IDEMPOTENT"; +})(MethodOptions_IdempotencyLevel || (MethodOptions_IdempotencyLevel = {})); +/** + * @generated from enum google.protobuf.FeatureSet.VisibilityFeature.DefaultSymbolVisibility + */ +var FeatureSet_VisibilityFeature_DefaultSymbolVisibility; +(function (FeatureSet_VisibilityFeature_DefaultSymbolVisibility) { + /** + * @generated from enum value: DEFAULT_SYMBOL_VISIBILITY_UNKNOWN = 0; + */ + FeatureSet_VisibilityFeature_DefaultSymbolVisibility[(FeatureSet_VisibilityFeature_DefaultSymbolVisibility["DEFAULT_SYMBOL_VISIBILITY_UNKNOWN"] = 0)] = "DEFAULT_SYMBOL_VISIBILITY_UNKNOWN"; + /** + * Default pre-EDITION_2024, all UNSET visibility are export. + * + * @generated from enum value: EXPORT_ALL = 1; + */ + FeatureSet_VisibilityFeature_DefaultSymbolVisibility[(FeatureSet_VisibilityFeature_DefaultSymbolVisibility["EXPORT_ALL"] = 1)] = "EXPORT_ALL"; + /** + * All top-level symbols default to export, nested default to local. + * + * @generated from enum value: EXPORT_TOP_LEVEL = 2; + */ + FeatureSet_VisibilityFeature_DefaultSymbolVisibility[(FeatureSet_VisibilityFeature_DefaultSymbolVisibility["EXPORT_TOP_LEVEL"] = 2)] = "EXPORT_TOP_LEVEL"; + /** + * All symbols default to local. + * + * @generated from enum value: LOCAL_ALL = 3; + */ + FeatureSet_VisibilityFeature_DefaultSymbolVisibility[(FeatureSet_VisibilityFeature_DefaultSymbolVisibility["LOCAL_ALL"] = 3)] = "LOCAL_ALL"; + /** + * All symbols local by default. Nested types cannot be exported. + * With special case caveat for message { enum {} reserved 1 to max; } + * This is the recommended setting for new protos. + * + * @generated from enum value: STRICT = 4; + */ + FeatureSet_VisibilityFeature_DefaultSymbolVisibility[(FeatureSet_VisibilityFeature_DefaultSymbolVisibility["STRICT"] = 4)] = "STRICT"; +})(FeatureSet_VisibilityFeature_DefaultSymbolVisibility || (FeatureSet_VisibilityFeature_DefaultSymbolVisibility = {})); +/** + * @generated from enum google.protobuf.FeatureSet.FieldPresence + */ +var FeatureSet_FieldPresence; +(function (FeatureSet_FieldPresence) { + /** + * @generated from enum value: FIELD_PRESENCE_UNKNOWN = 0; + */ + FeatureSet_FieldPresence[(FeatureSet_FieldPresence["FIELD_PRESENCE_UNKNOWN"] = 0)] = "FIELD_PRESENCE_UNKNOWN"; + /** + * @generated from enum value: EXPLICIT = 1; + */ + FeatureSet_FieldPresence[(FeatureSet_FieldPresence["EXPLICIT"] = 1)] = "EXPLICIT"; + /** + * @generated from enum value: IMPLICIT = 2; + */ + FeatureSet_FieldPresence[(FeatureSet_FieldPresence["IMPLICIT"] = 2)] = "IMPLICIT"; + /** + * @generated from enum value: LEGACY_REQUIRED = 3; + */ + FeatureSet_FieldPresence[(FeatureSet_FieldPresence["LEGACY_REQUIRED"] = 3)] = "LEGACY_REQUIRED"; +})(FeatureSet_FieldPresence || (FeatureSet_FieldPresence = {})); +/** + * @generated from enum google.protobuf.FeatureSet.EnumType + */ +var FeatureSet_EnumType; +(function (FeatureSet_EnumType) { + /** + * @generated from enum value: ENUM_TYPE_UNKNOWN = 0; + */ + FeatureSet_EnumType[(FeatureSet_EnumType["ENUM_TYPE_UNKNOWN"] = 0)] = "ENUM_TYPE_UNKNOWN"; + /** + * @generated from enum value: OPEN = 1; + */ + FeatureSet_EnumType[(FeatureSet_EnumType["OPEN"] = 1)] = "OPEN"; + /** + * @generated from enum value: CLOSED = 2; + */ + FeatureSet_EnumType[(FeatureSet_EnumType["CLOSED"] = 2)] = "CLOSED"; +})(FeatureSet_EnumType || (FeatureSet_EnumType = {})); +/** + * @generated from enum google.protobuf.FeatureSet.RepeatedFieldEncoding + */ +var FeatureSet_RepeatedFieldEncoding; +(function (FeatureSet_RepeatedFieldEncoding) { + /** + * @generated from enum value: REPEATED_FIELD_ENCODING_UNKNOWN = 0; + */ + FeatureSet_RepeatedFieldEncoding[(FeatureSet_RepeatedFieldEncoding["REPEATED_FIELD_ENCODING_UNKNOWN"] = 0)] = "REPEATED_FIELD_ENCODING_UNKNOWN"; + /** + * @generated from enum value: PACKED = 1; + */ + FeatureSet_RepeatedFieldEncoding[(FeatureSet_RepeatedFieldEncoding["PACKED"] = 1)] = "PACKED"; + /** + * @generated from enum value: EXPANDED = 2; + */ + FeatureSet_RepeatedFieldEncoding[(FeatureSet_RepeatedFieldEncoding["EXPANDED"] = 2)] = "EXPANDED"; +})(FeatureSet_RepeatedFieldEncoding || (FeatureSet_RepeatedFieldEncoding = {})); +/** + * @generated from enum google.protobuf.FeatureSet.Utf8Validation + */ +var FeatureSet_Utf8Validation; +(function (FeatureSet_Utf8Validation) { + /** + * @generated from enum value: UTF8_VALIDATION_UNKNOWN = 0; + */ + FeatureSet_Utf8Validation[(FeatureSet_Utf8Validation["UTF8_VALIDATION_UNKNOWN"] = 0)] = "UTF8_VALIDATION_UNKNOWN"; + /** + * @generated from enum value: VERIFY = 2; + */ + FeatureSet_Utf8Validation[(FeatureSet_Utf8Validation["VERIFY"] = 2)] = "VERIFY"; + /** + * @generated from enum value: NONE = 3; + */ + FeatureSet_Utf8Validation[(FeatureSet_Utf8Validation["NONE"] = 3)] = "NONE"; +})(FeatureSet_Utf8Validation || (FeatureSet_Utf8Validation = {})); +/** + * @generated from enum google.protobuf.FeatureSet.MessageEncoding + */ +var FeatureSet_MessageEncoding; +(function (FeatureSet_MessageEncoding) { + /** + * @generated from enum value: MESSAGE_ENCODING_UNKNOWN = 0; + */ + FeatureSet_MessageEncoding[(FeatureSet_MessageEncoding["MESSAGE_ENCODING_UNKNOWN"] = 0)] = "MESSAGE_ENCODING_UNKNOWN"; + /** + * @generated from enum value: LENGTH_PREFIXED = 1; + */ + FeatureSet_MessageEncoding[(FeatureSet_MessageEncoding["LENGTH_PREFIXED"] = 1)] = "LENGTH_PREFIXED"; + /** + * @generated from enum value: DELIMITED = 2; + */ + FeatureSet_MessageEncoding[(FeatureSet_MessageEncoding["DELIMITED"] = 2)] = "DELIMITED"; +})(FeatureSet_MessageEncoding || (FeatureSet_MessageEncoding = {})); +/** + * @generated from enum google.protobuf.FeatureSet.JsonFormat + */ +var FeatureSet_JsonFormat; +(function (FeatureSet_JsonFormat) { + /** + * @generated from enum value: JSON_FORMAT_UNKNOWN = 0; + */ + FeatureSet_JsonFormat[(FeatureSet_JsonFormat["JSON_FORMAT_UNKNOWN"] = 0)] = "JSON_FORMAT_UNKNOWN"; + /** + * @generated from enum value: ALLOW = 1; + */ + FeatureSet_JsonFormat[(FeatureSet_JsonFormat["ALLOW"] = 1)] = "ALLOW"; + /** + * @generated from enum value: LEGACY_BEST_EFFORT = 2; + */ + FeatureSet_JsonFormat[(FeatureSet_JsonFormat["LEGACY_BEST_EFFORT"] = 2)] = "LEGACY_BEST_EFFORT"; +})(FeatureSet_JsonFormat || (FeatureSet_JsonFormat = {})); +/** + * @generated from enum google.protobuf.FeatureSet.EnforceNamingStyle + */ +var FeatureSet_EnforceNamingStyle; +(function (FeatureSet_EnforceNamingStyle) { + /** + * @generated from enum value: ENFORCE_NAMING_STYLE_UNKNOWN = 0; + */ + FeatureSet_EnforceNamingStyle[(FeatureSet_EnforceNamingStyle["ENFORCE_NAMING_STYLE_UNKNOWN"] = 0)] = "ENFORCE_NAMING_STYLE_UNKNOWN"; + /** + * @generated from enum value: STYLE2024 = 1; + */ + FeatureSet_EnforceNamingStyle[(FeatureSet_EnforceNamingStyle["STYLE2024"] = 1)] = "STYLE2024"; + /** + * @generated from enum value: STYLE_LEGACY = 2; + */ + FeatureSet_EnforceNamingStyle[(FeatureSet_EnforceNamingStyle["STYLE_LEGACY"] = 2)] = "STYLE_LEGACY"; +})(FeatureSet_EnforceNamingStyle || (FeatureSet_EnforceNamingStyle = {})); +/** + * Represents the identified object's effect on the element in the original + * .proto file. + * + * @generated from enum google.protobuf.GeneratedCodeInfo.Annotation.Semantic + */ +var GeneratedCodeInfo_Annotation_Semantic; +(function (GeneratedCodeInfo_Annotation_Semantic) { + /** + * There is no effect or the effect is indescribable. + * + * @generated from enum value: NONE = 0; + */ + GeneratedCodeInfo_Annotation_Semantic[(GeneratedCodeInfo_Annotation_Semantic["NONE"] = 0)] = "NONE"; + /** + * The element is set or otherwise mutated. + * + * @generated from enum value: SET = 1; + */ + GeneratedCodeInfo_Annotation_Semantic[(GeneratedCodeInfo_Annotation_Semantic["SET"] = 1)] = "SET"; + /** + * An alias to the element is returned. + * + * @generated from enum value: ALIAS = 2; + */ + GeneratedCodeInfo_Annotation_Semantic[(GeneratedCodeInfo_Annotation_Semantic["ALIAS"] = 2)] = "ALIAS"; +})(GeneratedCodeInfo_Annotation_Semantic || (GeneratedCodeInfo_Annotation_Semantic = {})); +/** + * The full set of known editions. + * + * @generated from enum google.protobuf.Edition + */ +var Edition; +(function (Edition) { + /** + * A placeholder for an unknown edition value. + * + * @generated from enum value: EDITION_UNKNOWN = 0; + */ + Edition[(Edition["EDITION_UNKNOWN"] = 0)] = "EDITION_UNKNOWN"; + /** + * A placeholder edition for specifying default behaviors *before* a feature + * was first introduced. This is effectively an "infinite past". + * + * @generated from enum value: EDITION_LEGACY = 900; + */ + Edition[(Edition["EDITION_LEGACY"] = 900)] = "EDITION_LEGACY"; + /** + * Legacy syntax "editions". These pre-date editions, but behave much like + * distinct editions. These can't be used to specify the edition of proto + * files, but feature definitions must supply proto2/proto3 defaults for + * backwards compatibility. + * + * @generated from enum value: EDITION_PROTO2 = 998; + */ + Edition[(Edition["EDITION_PROTO2"] = 998)] = "EDITION_PROTO2"; + /** + * @generated from enum value: EDITION_PROTO3 = 999; + */ + Edition[(Edition["EDITION_PROTO3"] = 999)] = "EDITION_PROTO3"; + /** + * Editions that have been released. The specific values are arbitrary and + * should not be depended on, but they will always be time-ordered for easy + * comparison. + * + * @generated from enum value: EDITION_2023 = 1000; + */ + Edition[(Edition["EDITION_2023"] = 1000)] = "EDITION_2023"; + /** + * @generated from enum value: EDITION_2024 = 1001; + */ + Edition[(Edition["EDITION_2024"] = 1001)] = "EDITION_2024"; + /** + * A placeholder edition for developing and testing unscheduled features. + * + * @generated from enum value: EDITION_UNSTABLE = 9999; + */ + Edition[(Edition["EDITION_UNSTABLE"] = 9999)] = "EDITION_UNSTABLE"; + /** + * Placeholder editions for testing feature resolution. These should not be + * used or relied on outside of tests. + * + * @generated from enum value: EDITION_1_TEST_ONLY = 1; + */ + Edition[(Edition["EDITION_1_TEST_ONLY"] = 1)] = "EDITION_1_TEST_ONLY"; + /** + * @generated from enum value: EDITION_2_TEST_ONLY = 2; + */ + Edition[(Edition["EDITION_2_TEST_ONLY"] = 2)] = "EDITION_2_TEST_ONLY"; + /** + * @generated from enum value: EDITION_99997_TEST_ONLY = 99997; + */ + Edition[(Edition["EDITION_99997_TEST_ONLY"] = 99997)] = "EDITION_99997_TEST_ONLY"; + /** + * @generated from enum value: EDITION_99998_TEST_ONLY = 99998; + */ + Edition[(Edition["EDITION_99998_TEST_ONLY"] = 99998)] = "EDITION_99998_TEST_ONLY"; + /** + * @generated from enum value: EDITION_99999_TEST_ONLY = 99999; + */ + Edition[(Edition["EDITION_99999_TEST_ONLY"] = 99999)] = "EDITION_99999_TEST_ONLY"; + /** + * Placeholder for specifying unbounded edition support. This should only + * ever be used by plugins that can expect to never require any changes to + * support a new edition. + * + * @generated from enum value: EDITION_MAX = 2147483647; + */ + Edition[(Edition["EDITION_MAX"] = 2147483647)] = "EDITION_MAX"; +})(Edition || (Edition = {})); +/** + * Describes the 'visibility' of a symbol with respect to the proto import + * system. Symbols can only be imported when the visibility rules do not prevent + * it (ex: local symbols cannot be imported). Visibility modifiers can only set + * on `message` and `enum` as they are the only types available to be referenced + * from other files. + * + * @generated from enum google.protobuf.SymbolVisibility + */ +var SymbolVisibility; +(function (SymbolVisibility) { + /** + * @generated from enum value: VISIBILITY_UNSET = 0; + */ + SymbolVisibility[(SymbolVisibility["VISIBILITY_UNSET"] = 0)] = "VISIBILITY_UNSET"; + /** + * @generated from enum value: VISIBILITY_LOCAL = 1; + */ + SymbolVisibility[(SymbolVisibility["VISIBILITY_LOCAL"] = 1)] = "VISIBILITY_LOCAL"; + /** + * @generated from enum value: VISIBILITY_EXPORT = 2; + */ + SymbolVisibility[(SymbolVisibility["VISIBILITY_EXPORT"] = 2)] = "VISIBILITY_EXPORT"; +})(SymbolVisibility || (SymbolVisibility = {})); + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * @private Only exported for getExtension() + */ +function makeReadContext(options) { + return Object.assign(Object.assign({ readUnknownFields: true, recursionLimit: 100 }, options), { depth: 0 }); +} +/** + * Parse serialized binary data. + */ +function fromBinary(schema, bytes, options) { + const msg = reflect(schema, undefined, false); + readMessage(msg, new BinaryReader(bytes), makeReadContext(options), false, bytes.byteLength); + return msg.message; +} +/** + * If `delimited` is false, read the length given in `lengthOrDelimitedFieldNo`. + * + * If `delimited` is true, read until an EndGroup tag. `lengthOrDelimitedFieldNo` + * is the expected field number. + * + * @private + */ +function readMessage(message, reader, ctx, delimited, lengthOrDelimitedFieldNo) { + var _a; + if (++ctx.depth > ctx.recursionLimit) { + throw new Error(`cannot decode ${message.desc} from binary: maximum recursion depth of ${ctx.recursionLimit} reached`); + } + const end = delimited ? reader.len : reader.pos + lengthOrDelimitedFieldNo; + let fieldNo; + let wireType; + const unknownFields = (_a = message.getUnknown()) !== null && _a !== void 0 ? _a : []; + while (reader.pos < end) { + [fieldNo, wireType] = reader.tag(); + if (delimited && wireType == WireType.EndGroup) { + break; + } + const field = message.findNumber(fieldNo); + if (!field) { + // Use remaining recursion budget for skipping nested groups + const recursionLimit = ctx.recursionLimit - ctx.depth; + const data = reader.skip(wireType, fieldNo, recursionLimit); + if (ctx.readUnknownFields) { + unknownFields.push({ no: fieldNo, wireType, data }); + } + continue; + } + readField(message, reader, field, wireType, ctx); + } + if (delimited) { + if (wireType != WireType.EndGroup || fieldNo !== lengthOrDelimitedFieldNo) { + throw new Error("invalid end group tag"); + } + } + if (unknownFields.length > 0) { + message.setUnknown(unknownFields); + } + ctx.depth--; +} +/** + * @private Only exported for getExtension() + */ +function readField(message, reader, field, wireType, ctx) { + var _a; + switch (field.fieldKind) { + case "scalar": + message.set(field, readScalar(reader, field.scalar, field.utf8Validation)); + break; + case "enum": + const val = readScalar(reader, ScalarType.INT32); + if (field.enum.open) { + message.set(field, val); + } else { + const ok = field.enum.values.some((v) => v.number === val); + if (ok) { + message.set(field, val); + } else if (ctx.readUnknownFields) { + const bytes = []; + varint32write(val, bytes); + const unknownFields = (_a = message.getUnknown()) !== null && _a !== void 0 ? _a : []; + unknownFields.push({ + no: field.number, + wireType, + data: new Uint8Array(bytes), + }); + message.setUnknown(unknownFields); + } + } + break; + case "message": + message.set(field, readMessageField(reader, ctx, field, message.get(field))); + break; + case "list": + readListField(reader, wireType, message.get(field), ctx); + break; + case "map": + readMapEntry(reader, message.get(field), ctx); + break; + } +} +// Read a map field, expecting key field = 1, value field = 2 +function readMapEntry(reader, map, ctx) { + const field = map.field(); + let key; + let val; + // Read the length of the map entry, which is a varint. + const len = reader.uint32(); + // WARNING: Calculate end AFTER advancing reader.pos (above), so that + // reader.pos is at the start of the map entry. + const end = reader.pos + len; + while (reader.pos < end) { + const [fieldNo] = reader.tag(); + switch (fieldNo) { + case 1: + key = readScalar(reader, field.mapKey, field.utf8Validation); + break; + case 2: + switch (field.mapKind) { + case "scalar": + val = readScalar(reader, field.scalar, field.utf8Validation); + break; + case "enum": + val = reader.int32(); + break; + case "message": + val = readMessageField(reader, ctx, field); + break; + } + break; + } + } + if (key === undefined) { + key = scalarZeroValue(field.mapKey, false); + } + if (val === undefined) { + switch (field.mapKind) { + case "scalar": + val = scalarZeroValue(field.scalar, false); + break; + case "enum": + val = field.enum.values[0].number; + break; + case "message": + val = reflect(field.message, undefined, false); + break; + } + } + map.set(key, val); +} +function readListField(reader, wireType, list, ctx) { + var _a; + const field = list.field(); + if (field.listKind === "message") { + list.add(readMessageField(reader, ctx, field)); + return; + } + const scalarType = (_a = field.scalar) !== null && _a !== void 0 ? _a : ScalarType.INT32; + const packed = wireType == WireType.LengthDelimited && scalarType != ScalarType.STRING && scalarType != ScalarType.BYTES; + if (!packed) { + list.add(readScalar(reader, scalarType, field.utf8Validation)); + return; + } + const e = reader.uint32() + reader.pos; + while (reader.pos < e) { + list.add(readScalar(reader, scalarType, field.utf8Validation)); + } +} +function readMessageField(reader, ctx, field, mergeMessage) { + const delimited = field.delimitedEncoding; + const message = mergeMessage !== null && mergeMessage !== void 0 ? mergeMessage : reflect(field.message, undefined, false); + readMessage(message, reader, ctx, delimited, delimited ? field.number : reader.uint32()); + return message; +} +function readScalar(reader, type, validateUtf8 = false) { + switch (type) { + case ScalarType.STRING: + return reader.string(validateUtf8); + case ScalarType.BOOL: + return reader.bool(); + case ScalarType.DOUBLE: + return reader.double(); + case ScalarType.FLOAT: + return reader.float(); + case ScalarType.INT32: + return reader.int32(); + case ScalarType.INT64: + return reader.int64(); + case ScalarType.UINT64: + return reader.uint64(); + case ScalarType.FIXED64: + return reader.fixed64(); + case ScalarType.BYTES: + return reader.bytes(); + case ScalarType.FIXED32: + return reader.fixed32(); + case ScalarType.SFIXED32: + return reader.sfixed32(); + case ScalarType.SFIXED64: + return reader.sfixed64(); + case ScalarType.SINT64: + return reader.sint64(); + case ScalarType.UINT32: + return reader.uint32(); + case ScalarType.SINT32: + return reader.sint32(); + } +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Hydrate a file descriptor. + * + * @private + */ +function fileDesc(b64, imports) { + var _a; + const root = fromBinary(FileDescriptorProtoSchema, base64Decode(b64)); + root.messageType.forEach(restoreJsonNames); + root.dependency = (_a = imports === null || imports === void 0 ? void 0 : imports.map((f) => f.proto.name)) !== null && _a !== void 0 ? _a : []; + const reg = createFileRegistry(root, (protoFileName) => (imports === null || imports === void 0 ? void 0 : imports.find((f) => f.proto.name === protoFileName))); + // biome-ignore lint/style/noNonNullAssertion: non-null assertion because we just created the registry from the file we look up + return reg.getFile(root.name); +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Describes the file google/protobuf/timestamp.proto. + */ +const file_google_protobuf_timestamp = /*@__PURE__*/ fileDesc( + "Ch9nb29nbGUvcHJvdG9idWYvdGltZXN0YW1wLnByb3RvEg9nb29nbGUucHJvdG9idWYiKwoJVGltZXN0YW1wEg8KB3NlY29uZHMYASABKAMSDQoFbmFub3MYAiABKAVChQEKE2NvbS5nb29nbGUucHJvdG9idWZCDlRpbWVzdGFtcFByb3RvUAFaMmdvb2dsZS5nb2xhbmcub3JnL3Byb3RvYnVmL3R5cGVzL2tub3duL3RpbWVzdGFtcHBi+AEBogIDR1BCqgIeR29vZ2xlLlByb3RvYnVmLldlbGxLbm93blR5cGVzYgZwcm90bzM", +); + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +function anyIs(any, descOrTypeName) { + if (any.typeUrl === "") { + return false; + } + const want = typeof descOrTypeName == "string" ? descOrTypeName : descOrTypeName.typeName; + const got = typeUrlToName(any.typeUrl); + return want === got; +} +function anyUnpack(any, registryOrMessageDesc) { + if (any.typeUrl === "") { + return undefined; + } + const desc = registryOrMessageDesc.kind == "message" ? registryOrMessageDesc : registryOrMessageDesc.getMessage(typeUrlToName(any.typeUrl)); + if (!desc || !anyIs(any, desc)) { + return undefined; + } + return fromBinary(desc, any.value); +} +function typeUrlToName(url) { + const slash = url.lastIndexOf("/"); + const name = slash >= 0 ? url.substring(slash + 1) : url; + if (!name.length) { + throw new Error(`invalid type url: ${url}`); + } + return name; +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Describes the file google/protobuf/struct.proto. + */ +const file_google_protobuf_struct = /*@__PURE__*/ fileDesc( + "Chxnb29nbGUvcHJvdG9idWYvc3RydWN0LnByb3RvEg9nb29nbGUucHJvdG9idWYihAEKBlN0cnVjdBIzCgZmaWVsZHMYASADKAsyIy5nb29nbGUucHJvdG9idWYuU3RydWN0LkZpZWxkc0VudHJ5GkUKC0ZpZWxkc0VudHJ5EgsKA2tleRgBIAEoCRIlCgV2YWx1ZRgCIAEoCzIWLmdvb2dsZS5wcm90b2J1Zi5WYWx1ZToCOAEi6gEKBVZhbHVlEjAKCm51bGxfdmFsdWUYASABKA4yGi5nb29nbGUucHJvdG9idWYuTnVsbFZhbHVlSAASFgoMbnVtYmVyX3ZhbHVlGAIgASgBSAASFgoMc3RyaW5nX3ZhbHVlGAMgASgJSAASFAoKYm9vbF92YWx1ZRgEIAEoCEgAEi8KDHN0cnVjdF92YWx1ZRgFIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3RIABIwCgpsaXN0X3ZhbHVlGAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLkxpc3RWYWx1ZUgAQgYKBGtpbmQiMwoJTGlzdFZhbHVlEiYKBnZhbHVlcxgBIAMoCzIWLmdvb2dsZS5wcm90b2J1Zi5WYWx1ZSobCglOdWxsVmFsdWUSDgoKTlVMTF9WQUxVRRAAQn8KE2NvbS5nb29nbGUucHJvdG9idWZCC1N0cnVjdFByb3RvUAFaL2dvb2dsZS5nb2xhbmcub3JnL3Byb3RvYnVmL3R5cGVzL2tub3duL3N0cnVjdHBi+AEBogIDR1BCqgIeR29vZ2xlLlByb3RvYnVmLldlbGxLbm93blR5cGVzYgZwcm90bzM", +); +/** + * `NullValue` is a singleton enumeration to represent the null value for the + * `Value` type union. + * + * The JSON representation for `NullValue` is JSON `null`. + * + * @generated from enum google.protobuf.NullValue + */ +var NullValue; +(function (NullValue) { + /** + * Null value. + * + * @generated from enum value: NULL_VALUE = 0; + */ + NullValue[(NullValue["NULL_VALUE"] = 0)] = "NULL_VALUE"; +})(NullValue || (NullValue = {})); + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Compare two messages of the same type for equality. + * + * Fields are compared one by one, but only when set: a field set in one message + * but not the other makes them unequal, while fields set in both are compared + * by value. Extensions and unknown fields are disregarded by default. + * + * Float and double values are compared with IEEE semantics: NaN does not equal + * NaN, and -0 equals 0 (with the exception for singular fields with implicit + * presence: -0 is set, +0 is unset). + */ +function equals$1(schema, a, b, options) { + if (a.$typeName != schema.typeName || b.$typeName != schema.typeName) { + return false; + } + if (a === b) { + return true; + } + return reflectEquals(reflect(schema, a), reflect(schema, b), options); +} +function reflectEquals(a, b, opts) { + if (a.desc.typeName === "google.protobuf.Any" && void 0 == true) { + return anyUnpackedEquals(a.message, b.message, opts); + } + for (const f of a.fields) { + if (!fieldEquals(f, a, b, opts)) { + return false; + } + } + return true; +} +// TODO(tstamm) add an option to consider NaN equal to NaN? +function fieldEquals(f, a, b, opts) { + if (!a.isSet(f) && !b.isSet(f)) { + return true; + } + if (!a.isSet(f) || !b.isSet(f)) { + return false; + } + switch (f.fieldKind) { + case "scalar": + return scalarEquals(f.scalar, a.get(f), b.get(f)); + case "enum": + return a.get(f) === b.get(f); + case "message": + return reflectEquals(a.get(f), b.get(f), opts); + case "map": { + // TODO(tstamm) can't we compare sizes first? + const mapA = a.get(f); + const mapB = b.get(f); + const keys = []; + for (const k of mapA.keys()) { + if (!mapB.has(k)) { + return false; + } + keys.push(k); + } + for (const k of mapB.keys()) { + if (!mapA.has(k)) { + return false; + } + } + for (const key of keys) { + const va = mapA.get(key); + const vb = mapB.get(key); + if (va === vb) { + continue; + } + switch (f.mapKind) { + case "enum": + return false; + case "message": + if (!reflectEquals(va, vb, opts)) { + return false; + } + break; + case "scalar": + if (!scalarEquals(f.scalar, va, vb)) { + return false; + } + break; + } + } + break; + } + case "list": { + const listA = a.get(f); + const listB = b.get(f); + if (listA.size != listB.size) { + return false; + } + for (let i = 0; i < listA.size; i++) { + const va = listA.get(i); + const vb = listB.get(i); + if (va === vb) { + continue; + } + switch (f.listKind) { + case "enum": + return false; + case "message": + if (!reflectEquals(va, vb, opts)) { + return false; + } + break; + case "scalar": + if (!scalarEquals(f.scalar, va, vb)) { + return false; + } + break; + } + } + break; + } + } + return true; +} +function anyUnpackedEquals(a, b, opts) { + if (a.typeUrl !== b.typeUrl) { + return false; + } + const unpackedA = anyUnpack(a, opts.registry); + const unpackedB = anyUnpack(b, opts.registry); + if (unpackedA && unpackedB) { + const schema = opts.registry.getMessage(unpackedA.$typeName); + if (schema) { + return equals$1(schema, unpackedA, unpackedB, opts); + } + } + return scalarEquals(ScalarType.BYTES, a.value, b.value); +} + +// Copyright 2021-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * For any given message, create an object that is suitable for a Query Key in + * TanStack Query: + * + * - Default values are omitted (both implicit and explicit field presence). + * - NaN, Infinity, and -Infinity are converted to a string. + * - Uint8Array is encoded to a string with Base64. + * - BigInt values are converted to a string. + * - Properties are sorted by Protobuf source order. + * - Map keys are sorted with Array.sort. + * + * If pageParamKey is provided, omit the field with this name from the key. + */ +function createMessageKey(schema, value, pageParamKey) { + // eslint-disable-next-line @typescript-eslint/no-use-before-define -- circular reference + return messageKey(reflect(schema, create(schema, value)), pageParamKey?.toString()); +} +function scalarKey(value) { + if (typeof value == "bigint") { + return String(value); + } + if (typeof value == "number" && !isFinite(value)) { + return String(value); + } + if (value instanceof Uint8Array) { + return base64Encode(value, "std_raw"); + } + return value; +} +function listKey(list) { + const arr = Array.from(list); + const { listKind } = list.field(); + if (listKind == "scalar") { + return arr.map(scalarKey); + } + if (listKind == "message") { + // eslint-disable-next-line @typescript-eslint/no-use-before-define -- circular reference + return arr.map((m) => messageKey(m)); + } + return arr; +} +function mapKey(map) { + // eslint-disable-next-line @typescript-eslint/require-array-sort-compare -- we want the standard behavior + return Array.from(map.keys()) + .sort() + .reduce((result, k) => { + switch (map.field().mapKind) { + case "message": + // eslint-disable-next-line @typescript-eslint/no-use-before-define -- circular reference + result[k] = messageKey(map.get(k)); + break; + case "scalar": + result[k] = scalarKey(map.get(k)); + break; + case "enum": + result[k] = map.get(k); + break; + } + return result; + }, {}); +} +function messageKey(message, pageParamKey) { + const result = {}; + for (const f of message.sortedFields) { + if (!message.isSet(f)) { + continue; + } + if (f.localName === pageParamKey) { + continue; + } + switch (f.fieldKind) { + case "scalar": + result[f.localName] = scalarKey(message.get(f)); + break; + case "enum": + result[f.localName] = message.get(f); + break; + case "list": + result[f.localName] = listKey(message.get(f)); + break; + case "map": + result[f.localName] = mapKey(message.get(f)); + break; + case "message": + result[f.localName] = messageKey(message.get(f)); + break; + } + } + return result; +} + +// Copyright 2021-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +const staticKeySymbol = Symbol("static-key"); +const transportKeys = new WeakMap(); +let counter = 0; +/** + * For a given Transport, create a string key that is suitable for a Query Key + * in TanStack Query. + * + * This function will return a unique string for every reference. + */ +function createTransportKey(transport) { + if (transport[staticKeySymbol] !== undefined) { + return transport[staticKeySymbol]; + } + let key = transportKeys.get(transport); + if (key === undefined) { + key = `t${++counter}`; + transportKeys.set(transport, key); + } + return key; +} + +// Copyright 2021-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +function createConnectQueryKey(params) { + const props = + params.schema.kind == "rpc" ? + { + serviceName: params.schema.parent.typeName, + methodName: params.schema.name, + } + : { + serviceName: params.schema.typeName, + }; + if (params.transport !== undefined) { + props.transport = createTransportKey(params.transport); + } + if (params.cardinality !== undefined) { + props.cardinality = params.cardinality; + } + if (params.schema.kind == "rpc" && "input" in params) { + if (typeof params.input == "symbol") { + props.input = "skipped"; + } else if (params.input !== undefined) { + props.input = createMessageKey(params.schema.input, params.input, params.pageParamKey); + } + } + if (params.schema.kind === "rpc" && "headers" in params && params.headers !== undefined) { + props.headers = createHeadersKey(params.headers); + } + return ["connect-query", props]; +} +/** + * Creates a record of headers from a HeadersInit object. + * + */ +function createHeadersKey(headers) { + const result = {}; + for (const [key, value] of new Headers(headers)) { + result[key] = value; + } + return result; +} + +// Copyright 2021-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Throws an error with the provided message when the condition is `false` + */ +function assert(condition, message) { + if (!condition) { + throw new Error(`Invalid assertion: ${message}`); + } +} + +// Copyright 2021-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Call a unary method given its signature and input. + */ +// eslint-disable-next-line @typescript-eslint/max-params -- 4th param is optional +async function callUnaryMethod(transport, schema, input, options) { + const result = await transport.unary(schema, options?.signal, undefined, options?.headers, input ?? create(schema.input), undefined); + return result.message; +} + +// src/subscribable.ts +var Subscribable = class { + constructor() { + this.listeners = /* @__PURE__ */ new Set(); + this.subscribe = this.subscribe.bind(this); + } + subscribe(listener) { + this.listeners.add(listener); + this.onSubscribe(); + return () => { + this.listeners.delete(listener); + this.onUnsubscribe(); + }; + } + hasListeners() { + return this.listeners.size > 0; + } + onSubscribe() {} + onUnsubscribe() {} +}; + +// src/focusManager.ts +var FocusManager = class extends Subscribable { + #focused; + #cleanup; + #setup; + constructor() { + super(); + this.#setup = (onFocus) => { + if (typeof window !== "undefined" && window.addEventListener) { + const listener = () => onFocus(); + window.addEventListener("visibilitychange", listener, false); + return () => { + window.removeEventListener("visibilitychange", listener); + }; + } + return; + }; + } + onSubscribe() { + if (!this.#cleanup) { + this.setEventListener(this.#setup); + } + } + onUnsubscribe() { + if (!this.hasListeners()) { + this.#cleanup?.(); + this.#cleanup = void 0; + } + } + setEventListener(setup) { + this.#setup = setup; + this.#cleanup?.(); + this.#cleanup = setup((focused) => { + if (typeof focused === "boolean") { + this.setFocused(focused); + } else { + this.onFocus(); + } + }); + } + setFocused(focused) { + const changed = this.#focused !== focused; + if (changed) { + this.#focused = focused; + this.onFocus(); + } + } + onFocus() { + const isFocused = this.isFocused(); + this.listeners.forEach((listener) => { + listener(isFocused); + }); + } + isFocused() { + if (typeof this.#focused === "boolean") { + return this.#focused; + } + return globalThis.document?.visibilityState !== "hidden"; + } +}; +var focusManager = new FocusManager(); + +var defaultTimeoutProvider = { + // We need the wrapper function syntax below instead of direct references to + // global setTimeout etc. + // + // BAD: `setTimeout: setTimeout` + // GOOD: `setTimeout: (cb, delay) => setTimeout(cb, delay)` + // + // If we use direct references here, then anything that wants to spy on or + // replace the global setTimeout (like tests) won't work since we'll already + // have a hard reference to the original implementation at the time when this + // file was imported. + setTimeout: (callback, delay) => setTimeout(callback, delay), + clearTimeout: (timeoutId) => clearTimeout(timeoutId), + setInterval: (callback, delay) => setInterval(callback, delay), + clearInterval: (intervalId) => clearInterval(intervalId), +}; +var TimeoutManager = class { + // We cannot have TimeoutManager as we must instantiate it with a concrete + // type at app boot; and if we leave that type, then any new timer provider + // would need to support the default provider's concrete timer ID, which is + // infeasible across environments. + // + // We settle for type safety for the TimeoutProvider type, and accept that + // this class is unsafe internally to allow for extension. + #provider = defaultTimeoutProvider; + #providerCalled = false; + setTimeoutProvider(provider) { + this.#provider = provider; + } + setTimeout(callback, delay) { + return this.#provider.setTimeout(callback, delay); + } + clearTimeout(timeoutId) { + this.#provider.clearTimeout(timeoutId); + } + setInterval(callback, delay) { + return this.#provider.setInterval(callback, delay); + } + clearInterval(intervalId) { + this.#provider.clearInterval(intervalId); + } +}; +var timeoutManager = new TimeoutManager(); +function systemSetTimeoutZero(callback) { + setTimeout(callback, 0); +} + +var isServer = typeof window === "undefined" || "Deno" in globalThis; +function noop$1() {} +function isValidTimeout(value) { + return typeof value === "number" && value >= 0 && value !== Infinity; +} +function timeUntilStale(updatedAt, staleTime) { + return Math.max(updatedAt + (staleTime || 0) - Date.now(), 0); +} +function resolveStaleTime(staleTime, query) { + return typeof staleTime === "function" ? staleTime(query) : staleTime; +} +function resolveQueryBoolean(option, query) { + return typeof option === "function" ? option(query) : option; +} +function hashKey(queryKey) { + return JSON.stringify(queryKey, (_, val) => + isPlainObject(val) ? + Object.keys(val) + .sort() + .reduce((result, key) => { + result[key] = val[key]; + return result; + }, {}) + : val, + ); +} +var hasOwn = Object.prototype.hasOwnProperty; +function replaceEqualDeep(a, b, depth = 0) { + if (a === b) { + return a; + } + if (depth > 500) return b; + const array = isPlainArray(a) && isPlainArray(b); + if (!array && !(isPlainObject(a) && isPlainObject(b))) return b; + const aItems = array ? a : Object.keys(a); + const aSize = aItems.length; + const bItems = array ? b : Object.keys(b); + const bSize = bItems.length; + const copy = array ? new Array(bSize) : {}; + let equalItems = 0; + for (let i = 0; i < bSize; i++) { + const key = array ? i : bItems[i]; + const aItem = a[key]; + const bItem = b[key]; + if (aItem === bItem) { + copy[key] = aItem; + if (array ? i < aSize : hasOwn.call(a, key)) equalItems++; + continue; + } + if (aItem === null || bItem === null || typeof aItem !== "object" || typeof bItem !== "object") { + copy[key] = bItem; + continue; + } + const v = replaceEqualDeep(aItem, bItem, depth + 1); + copy[key] = v; + if (v === aItem) equalItems++; + } + return aSize === bSize && equalItems === aSize ? a : copy; +} +function shallowEqualObjects(a, b) { + if (!b || Object.keys(a).length !== Object.keys(b).length) { + return false; + } + for (const key in a) { + if (a[key] !== b[key]) { + return false; + } + } + return true; +} +function isPlainArray(value) { + return Array.isArray(value) && value.length === Object.keys(value).length; +} +function isPlainObject(o) { + if (!hasObjectPrototype(o)) { + return false; + } + const ctor = o.constructor; + if (ctor === void 0) { + return true; + } + const prot = ctor.prototype; + if (!hasObjectPrototype(prot)) { + return false; + } + if (!prot.hasOwnProperty("isPrototypeOf")) { + return false; + } + if (Object.getPrototypeOf(o) !== Object.prototype) { + return false; + } + return true; +} +function hasObjectPrototype(o) { + return Object.prototype.toString.call(o) === "[object Object]"; +} +function replaceData(prevData, data, options) { + if (typeof options.structuralSharing === "function") { + return options.structuralSharing(prevData, data); + } else if (options.structuralSharing !== false) { + return replaceEqualDeep(prevData, data); + } + return data; +} +function keepPreviousData(previousData) { + return previousData; +} +var skipToken = /* @__PURE__ */ Symbol(); +function shouldThrowError(throwOnError, params) { + if (typeof throwOnError === "function") { + return throwOnError(...params); + } + return !!throwOnError; +} + +// src/environmentManager.ts +var environmentManager = /* @__PURE__ */ (() => { + let isServerFn = () => isServer; + return { + /** + * Returns whether the current runtime should be treated as a server environment. + */ + isServer() { + return isServerFn(); + }, + /** + * Overrides the server check globally. + */ + setIsServer(isServerValue) { + isServerFn = isServerValue; + }, + }; +})(); + +// src/thenable.ts +function pendingThenable() { + let resolve; + let reject; + const thenable = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + thenable.status = "pending"; + thenable.catch(() => {}); + function finalize(data) { + Object.assign(thenable, data); + delete thenable.resolve; + delete thenable.reject; + } + thenable.resolve = (value) => { + finalize({ + status: "fulfilled", + value, + }); + resolve(value); + }; + thenable.reject = (reason) => { + finalize({ + status: "rejected", + reason, + }); + reject(reason); + }; + return thenable; +} + +// src/notifyManager.ts +var defaultScheduler = systemSetTimeoutZero; +function createNotifyManager() { + let queue = []; + let transactions = 0; + let notifyFn = (callback) => { + callback(); + }; + let batchNotifyFn = (callback) => { + callback(); + }; + let scheduleFn = defaultScheduler; + const schedule = (callback) => { + if (transactions) { + queue.push(callback); + } else { + scheduleFn(() => { + notifyFn(callback); + }); + } + }; + const flush = () => { + const originalQueue = queue; + queue = []; + if (originalQueue.length) { + scheduleFn(() => { + batchNotifyFn(() => { + originalQueue.forEach((callback) => { + notifyFn(callback); + }); + }); + }); + } + }; + return { + batch: (callback) => { + let result; + transactions++; + try { + result = callback(); + } finally { + transactions--; + if (!transactions) { + flush(); + } + } + return result; + }, + /** + * All calls to the wrapped function will be batched. + */ + batchCalls: (callback) => { + return (...args) => { + schedule(() => { + callback(...args); + }); + }; + }, + schedule, + /** + * Use this method to set a custom notify function. + * This can be used to for example wrap notifications with `React.act` while running tests. + */ + setNotifyFunction: (fn) => { + notifyFn = fn; + }, + /** + * Use this method to set a custom function to batch notifications together into a single tick. + * By default React Query will use the batch function provided by ReactDOM or React Native. + */ + setBatchNotifyFunction: (fn) => { + batchNotifyFn = fn; + }, + setScheduler: (fn) => { + scheduleFn = fn; + }, + }; +} +var notifyManager = createNotifyManager(); + +// src/onlineManager.ts +var OnlineManager = class extends Subscribable { + #online = true; + #cleanup; + #setup; + constructor() { + super(); + this.#setup = (onOnline) => { + if (typeof window !== "undefined" && window.addEventListener) { + const onlineListener = () => onOnline(true); + const offlineListener = () => onOnline(false); + window.addEventListener("online", onlineListener, false); + window.addEventListener("offline", offlineListener, false); + return () => { + window.removeEventListener("online", onlineListener); + window.removeEventListener("offline", offlineListener); + }; + } + return; + }; + } + onSubscribe() { + if (!this.#cleanup) { + this.setEventListener(this.#setup); + } + } + onUnsubscribe() { + if (!this.hasListeners()) { + this.#cleanup?.(); + this.#cleanup = void 0; + } + } + setEventListener(setup) { + this.#setup = setup; + this.#cleanup?.(); + this.#cleanup = setup(this.setOnline.bind(this)); + } + setOnline(online) { + const changed = this.#online !== online; + if (changed) { + this.#online = online; + this.listeners.forEach((listener) => { + listener(online); + }); + } + } + isOnline() { + return this.#online; + } +}; +var onlineManager = new OnlineManager(); + +// src/retryer.ts +function canFetch(networkMode) { + return (networkMode ?? "online") === "online" ? onlineManager.isOnline() : true; +} + +// src/infiniteQueryBehavior.ts +function getNextPageParam(options, { pages, pageParams }) { + const lastIndex = pages.length - 1; + return pages.length > 0 ? options.getNextPageParam(pages[lastIndex], pages, pageParams[lastIndex], pageParams) : void 0; +} +function getPreviousPageParam(options, { pages, pageParams }) { + return pages.length > 0 ? options.getPreviousPageParam?.(pages[0], pages, pageParams[0], pageParams) : void 0; +} +function hasNextPage(options, data) { + if (!data) return false; + return getNextPageParam(options, data) != null; +} +function hasPreviousPage(options, data) { + if (!data || !options.getPreviousPageParam) return false; + return getPreviousPageParam(options, data) != null; +} + +function fetchState(data, options) { + return { + fetchFailureCount: 0, + fetchFailureReason: null, + fetchStatus: canFetch(options.networkMode) ? "fetching" : "paused", + ...(data === void 0 && { + error: null, + status: "pending", + }), + }; +} + +// src/queryObserver.ts +var QueryObserver = class extends Subscribable { + constructor(client, options) { + super(); + this.options = options; + this.#client = client; + this.#selectError = null; + this.#currentThenable = pendingThenable(); + this.bindMethods(); + this.setOptions(options); + } + #client; + #currentQuery = void 0; + #currentQueryInitialState = void 0; + #currentResult = void 0; + #currentResultState; + #currentResultOptions; + #currentThenable; + #selectError; + #selectFn; + #selectResult; + // This property keeps track of the last query with defined data. + // It will be used to pass the previous data and query to the placeholder function between renders. + #lastQueryWithDefinedData; + #staleTimeoutId; + #refetchIntervalId; + #currentRefetchInterval; + #trackedProps = /* @__PURE__ */ new Set(); + bindMethods() { + this.refetch = this.refetch.bind(this); + } + onSubscribe() { + if (this.listeners.size === 1) { + this.#currentQuery.addObserver(this); + if (shouldFetchOnMount(this.#currentQuery, this.options)) { + this.#executeFetch(); + } else { + this.updateResult(); + } + this.#updateTimers(); + } + } + onUnsubscribe() { + if (!this.hasListeners()) { + this.destroy(); + } + } + shouldFetchOnReconnect() { + return shouldFetchOn(this.#currentQuery, this.options, this.options.refetchOnReconnect); + } + shouldFetchOnWindowFocus() { + return shouldFetchOn(this.#currentQuery, this.options, this.options.refetchOnWindowFocus); + } + destroy() { + this.listeners = /* @__PURE__ */ new Set(); + this.#clearStaleTimeout(); + this.#clearRefetchInterval(); + this.#currentQuery.removeObserver(this); + } + setOptions(options) { + const prevOptions = this.options; + const prevQuery = this.#currentQuery; + this.options = this.#client.defaultQueryOptions(options); + if ( + this.options.enabled !== void 0 && + typeof this.options.enabled !== "boolean" && + typeof this.options.enabled !== "function" && + typeof resolveQueryBoolean(this.options.enabled, this.#currentQuery) !== "boolean" + ) { + throw new Error("Expected enabled to be a boolean or a callback that returns a boolean"); + } + this.#updateQuery(); + this.#currentQuery.setOptions(this.options); + if (prevOptions._defaulted && !shallowEqualObjects(this.options, prevOptions)) { + this.#client.getQueryCache().notify({ + type: "observerOptionsUpdated", + query: this.#currentQuery, + observer: this, + }); + } + const mounted = this.hasListeners(); + if (mounted && shouldFetchOptionally(this.#currentQuery, prevQuery, this.options, prevOptions)) { + this.#executeFetch(); + } + this.updateResult(); + if ( + mounted && + (this.#currentQuery !== prevQuery || + resolveQueryBoolean(this.options.enabled, this.#currentQuery) !== resolveQueryBoolean(prevOptions.enabled, this.#currentQuery) || + resolveStaleTime(this.options.staleTime, this.#currentQuery) !== resolveStaleTime(prevOptions.staleTime, this.#currentQuery)) + ) { + this.#updateStaleTimeout(); + } + const nextRefetchInterval = this.#computeRefetchInterval(); + if ( + mounted && + (this.#currentQuery !== prevQuery || + resolveQueryBoolean(this.options.enabled, this.#currentQuery) !== resolveQueryBoolean(prevOptions.enabled, this.#currentQuery) || + nextRefetchInterval !== this.#currentRefetchInterval) + ) { + this.#updateRefetchInterval(nextRefetchInterval); + } + } + getOptimisticResult(options) { + const query = this.#client.getQueryCache().build(this.#client, options); + const result = this.createResult(query, options); + if (shouldAssignObserverCurrentProperties(this, result)) { + this.#currentResult = result; + this.#currentResultOptions = this.options; + this.#currentResultState = this.#currentQuery.state; + } + return result; + } + getCurrentResult() { + return this.#currentResult; + } + trackResult(result, onPropTracked) { + return new Proxy(result, { + get: (target, key) => { + this.trackProp(key); + onPropTracked?.(key); + if (key === "promise") { + this.trackProp("data"); + if (!this.options.experimental_prefetchInRender && this.#currentThenable.status === "pending") { + this.#currentThenable.reject(new Error("experimental_prefetchInRender feature flag is not enabled")); + } + } + return Reflect.get(target, key); + }, + }); + } + trackProp(key) { + this.#trackedProps.add(key); + } + getCurrentQuery() { + return this.#currentQuery; + } + refetch({ ...options } = {}) { + return this.fetch({ + ...options, + }); + } + fetchOptimistic(options) { + const defaultedOptions = this.#client.defaultQueryOptions(options); + const query = this.#client.getQueryCache().build(this.#client, defaultedOptions); + return query.fetch().then(() => this.createResult(query, defaultedOptions)); + } + fetch(fetchOptions) { + return this.#executeFetch({ + ...fetchOptions, + cancelRefetch: fetchOptions.cancelRefetch ?? true, + }).then(() => { + this.updateResult(); + return this.#currentResult; + }); + } + #executeFetch(fetchOptions) { + this.#updateQuery(); + let promise = this.#currentQuery.fetch(this.options, fetchOptions); + if (!fetchOptions?.throwOnError) { + promise = promise.catch(noop$1); + } + return promise; + } + #updateStaleTimeout() { + this.#clearStaleTimeout(); + const staleTime = resolveStaleTime(this.options.staleTime, this.#currentQuery); + if (environmentManager.isServer() || this.#currentResult.isStale || !isValidTimeout(staleTime)) { + return; + } + const time = timeUntilStale(this.#currentResult.dataUpdatedAt, staleTime); + const timeout = time + 1; + this.#staleTimeoutId = timeoutManager.setTimeout(() => { + if (!this.#currentResult.isStale) { + this.updateResult(); + } + }, timeout); + } + #computeRefetchInterval() { + return (typeof this.options.refetchInterval === "function" ? this.options.refetchInterval(this.#currentQuery) : this.options.refetchInterval) ?? false; + } + #updateRefetchInterval(nextInterval) { + this.#clearRefetchInterval(); + this.#currentRefetchInterval = nextInterval; + if ( + environmentManager.isServer() || + resolveQueryBoolean(this.options.enabled, this.#currentQuery) === false || + !isValidTimeout(this.#currentRefetchInterval) || + this.#currentRefetchInterval === 0 + ) { + return; + } + this.#refetchIntervalId = timeoutManager.setInterval(() => { + if (this.options.refetchIntervalInBackground || focusManager.isFocused()) { + this.#executeFetch(); + } + }, this.#currentRefetchInterval); + } + #updateTimers() { + this.#updateStaleTimeout(); + this.#updateRefetchInterval(this.#computeRefetchInterval()); + } + #clearStaleTimeout() { + if (this.#staleTimeoutId !== void 0) { + timeoutManager.clearTimeout(this.#staleTimeoutId); + this.#staleTimeoutId = void 0; + } + } + #clearRefetchInterval() { + if (this.#refetchIntervalId !== void 0) { + timeoutManager.clearInterval(this.#refetchIntervalId); + this.#refetchIntervalId = void 0; + } + } + createResult(query, options) { + const prevQuery = this.#currentQuery; + const prevOptions = this.options; + const prevResult = this.#currentResult; + const prevResultState = this.#currentResultState; + const prevResultOptions = this.#currentResultOptions; + const queryChange = query !== prevQuery; + const queryInitialState = queryChange ? query.state : this.#currentQueryInitialState; + const { state } = query; + let newState = { ...state }; + let isPlaceholderData = false; + let data; + if (options._optimisticResults) { + const mounted = this.hasListeners(); + const fetchOnMount = !mounted && shouldFetchOnMount(query, options); + const fetchOptionally = mounted && shouldFetchOptionally(query, prevQuery, options, prevOptions); + if (fetchOnMount || fetchOptionally) { + newState = { + ...newState, + ...fetchState(state.data, query.options), + }; + } + if (options._optimisticResults === "isRestoring") { + newState.fetchStatus = "idle"; + } + } + let { error, errorUpdatedAt, status } = newState; + data = newState.data; + let skipSelect = false; + if (options.placeholderData !== void 0 && data === void 0 && status === "pending") { + let placeholderData; + if (prevResult?.isPlaceholderData && options.placeholderData === prevResultOptions?.placeholderData) { + placeholderData = prevResult.data; + skipSelect = true; + } else { + placeholderData = + typeof options.placeholderData === "function" ? options.placeholderData(this.#lastQueryWithDefinedData?.state.data, this.#lastQueryWithDefinedData) : options.placeholderData; + } + if (placeholderData !== void 0) { + status = "success"; + data = replaceData(prevResult?.data, placeholderData, options); + isPlaceholderData = true; + } + } + if (options.select && data !== void 0 && !skipSelect) { + if (prevResult && data === prevResultState?.data && options.select === this.#selectFn) { + data = this.#selectResult; + } else { + try { + this.#selectFn = options.select; + data = options.select(data); + data = replaceData(prevResult?.data, data, options); + this.#selectResult = data; + this.#selectError = null; + } catch (selectError) { + this.#selectError = selectError; + } + } + } + if (this.#selectError) { + error = this.#selectError; + data = this.#selectResult; + errorUpdatedAt = Date.now(); + status = "error"; + } + const isFetching = newState.fetchStatus === "fetching"; + const isPending = status === "pending"; + const isError = status === "error"; + const isLoading = isPending && isFetching; + const hasData = data !== void 0; + const result = { + status, + fetchStatus: newState.fetchStatus, + isPending, + isSuccess: status === "success", + isError, + isInitialLoading: isLoading, + isLoading, + data, + dataUpdatedAt: newState.dataUpdatedAt, + error, + errorUpdatedAt, + failureCount: newState.fetchFailureCount, + failureReason: newState.fetchFailureReason, + errorUpdateCount: newState.errorUpdateCount, + isFetched: query.isFetched(), + isFetchedAfterMount: newState.dataUpdateCount > queryInitialState.dataUpdateCount || newState.errorUpdateCount > queryInitialState.errorUpdateCount, + isFetching, + isRefetching: isFetching && !isPending, + isLoadingError: isError && !hasData, + isPaused: newState.fetchStatus === "paused", + isPlaceholderData, + isRefetchError: isError && hasData, + isStale: isStale(query, options), + refetch: this.refetch, + promise: this.#currentThenable, + isEnabled: resolveQueryBoolean(options.enabled, query) !== false, + }; + const nextResult = result; + if (this.options.experimental_prefetchInRender) { + const hasResultData = nextResult.data !== void 0; + const isErrorWithoutData = nextResult.status === "error" && !hasResultData; + const finalizeThenableIfPossible = (thenable) => { + if (isErrorWithoutData) { + thenable.reject(nextResult.error); + } else if (hasResultData) { + thenable.resolve(nextResult.data); + } + }; + const recreateThenable = () => { + const pending = (this.#currentThenable = nextResult.promise = pendingThenable()); + finalizeThenableIfPossible(pending); + }; + const prevThenable = this.#currentThenable; + switch (prevThenable.status) { + case "pending": + if (query.queryHash === prevQuery.queryHash) { + finalizeThenableIfPossible(prevThenable); + } + break; + case "fulfilled": + if (isErrorWithoutData || nextResult.data !== prevThenable.value) { + recreateThenable(); + } + break; + case "rejected": + if (!isErrorWithoutData || nextResult.error !== prevThenable.reason) { + recreateThenable(); + } + break; + } + } + return nextResult; + } + updateResult() { + const prevResult = this.#currentResult; + const nextResult = this.createResult(this.#currentQuery, this.options); + this.#currentResultState = this.#currentQuery.state; + this.#currentResultOptions = this.options; + if (this.#currentResultState.data !== void 0) { + this.#lastQueryWithDefinedData = this.#currentQuery; + } + if (shallowEqualObjects(nextResult, prevResult)) { + return; + } + this.#currentResult = nextResult; + const shouldNotifyListeners = () => { + if (!prevResult) { + return true; + } + const { notifyOnChangeProps } = this.options; + const notifyOnChangePropsValue = typeof notifyOnChangeProps === "function" ? notifyOnChangeProps() : notifyOnChangeProps; + if (notifyOnChangePropsValue === "all" || (!notifyOnChangePropsValue && !this.#trackedProps.size)) { + return true; + } + const includedProps = new Set(notifyOnChangePropsValue ?? this.#trackedProps); + if (this.options.throwOnError) { + includedProps.add("error"); + } + return Object.keys(this.#currentResult).some((key) => { + const typedKey = key; + const changed = this.#currentResult[typedKey] !== prevResult[typedKey]; + return changed && includedProps.has(typedKey); + }); + }; + this.#notify({ listeners: shouldNotifyListeners() }); + } + #updateQuery() { + const query = this.#client.getQueryCache().build(this.#client, this.options); + if (query === this.#currentQuery) { + return; + } + const prevQuery = this.#currentQuery; + this.#currentQuery = query; + this.#currentQueryInitialState = query.state; + if (this.hasListeners()) { + prevQuery?.removeObserver(this); + query.addObserver(this); + } + } + onQueryUpdate() { + this.updateResult(); + if (this.hasListeners()) { + this.#updateTimers(); + } + } + #notify(notifyOptions) { + notifyManager.batch(() => { + if (notifyOptions.listeners) { + this.listeners.forEach((listener) => { + listener(this.#currentResult); + }); + } + this.#client.getQueryCache().notify({ + query: this.#currentQuery, + type: "observerResultsUpdated", + }); + }); + } +}; +function shouldLoadOnMount(query, options) { + return resolveQueryBoolean(options.enabled, query) !== false && query.state.data === void 0 && !(query.state.status === "error" && resolveQueryBoolean(options.retryOnMount, query) === false); +} +function shouldFetchOnMount(query, options) { + return shouldLoadOnMount(query, options) || (query.state.data !== void 0 && shouldFetchOn(query, options, options.refetchOnMount)); +} +function shouldFetchOn(query, options, field) { + if (resolveQueryBoolean(options.enabled, query) !== false && resolveStaleTime(options.staleTime, query) !== "static") { + const value = typeof field === "function" ? field(query) : field; + return value === "always" || (value !== false && isStale(query, options)); + } + return false; +} +function shouldFetchOptionally(query, prevQuery, options, prevOptions) { + return (query !== prevQuery || resolveQueryBoolean(prevOptions.enabled, query) === false) && (!options.suspense || query.state.status !== "error") && isStale(query, options); +} +function isStale(query, options) { + return resolveQueryBoolean(options.enabled, query) !== false && query.isStaleByTime(resolveStaleTime(options.staleTime, query)); +} +function shouldAssignObserverCurrentProperties(observer, optimisticResult) { + if (!shallowEqualObjects(observer.getCurrentResult(), optimisticResult)) { + return true; + } + return false; +} + +// src/infiniteQueryObserver.ts +var InfiniteQueryObserver = class extends QueryObserver { + constructor(client, options) { + super(client, options); + } + bindMethods() { + super.bindMethods(); + this.fetchNextPage = this.fetchNextPage.bind(this); + this.fetchPreviousPage = this.fetchPreviousPage.bind(this); + } + setOptions(options) { + options._type = "infinite"; + super.setOptions(options); + } + getOptimisticResult(options) { + options._type = "infinite"; + return super.getOptimisticResult(options); + } + fetchNextPage(options) { + return this.fetch({ + ...options, + meta: { + fetchMore: { direction: "forward" }, + }, + }); + } + fetchPreviousPage(options) { + return this.fetch({ + ...options, + meta: { + fetchMore: { direction: "backward" }, + }, + }); + } + createResult(query, options) { + const { state } = query; + const parentResult = super.createResult(query, options); + const { isFetching, isRefetching, isError, isRefetchError } = parentResult; + const fetchDirection = state.fetchMeta?.fetchMore?.direction; + const isFetchNextPageError = isError && fetchDirection === "forward"; + const isFetchingNextPage = isFetching && fetchDirection === "forward"; + const isFetchPreviousPageError = isError && fetchDirection === "backward"; + const isFetchingPreviousPage = isFetching && fetchDirection === "backward"; + const result = { + ...parentResult, + fetchNextPage: this.fetchNextPage, + fetchPreviousPage: this.fetchPreviousPage, + hasNextPage: hasNextPage(options, state.data), + hasPreviousPage: hasPreviousPage(options, state.data), + isFetchNextPageError, + isFetchingNextPage, + isFetchPreviousPageError, + isFetchingPreviousPage, + isRefetchError: isRefetchError && !isFetchNextPageError && !isFetchPreviousPageError, + isRefetching: isRefetching && !isFetchingNextPage && !isFetchingPreviousPage, + }; + return result; + } +}; + +// src/mutation.ts +function getDefaultState() { + return { + context: void 0, + data: void 0, + error: null, + failureCount: 0, + failureReason: null, + isPaused: false, + status: "idle", + variables: void 0, + submittedAt: 0, + }; +} + +// src/mutationObserver.ts +var MutationObserver = class extends Subscribable { + #client; + #currentResult = void 0; + #currentMutation; + #mutateOptions; + constructor(client, options) { + super(); + this.#client = client; + this.setOptions(options); + this.bindMethods(); + this.#updateResult(); + } + bindMethods() { + this.mutate = this.mutate.bind(this); + this.reset = this.reset.bind(this); + } + setOptions(options) { + const prevOptions = this.options; + this.options = this.#client.defaultMutationOptions(options); + if (!shallowEqualObjects(this.options, prevOptions)) { + this.#client.getMutationCache().notify({ + type: "observerOptionsUpdated", + mutation: this.#currentMutation, + observer: this, + }); + } + if (prevOptions?.mutationKey && this.options.mutationKey && hashKey(prevOptions.mutationKey) !== hashKey(this.options.mutationKey)) { + this.reset(); + } else if (this.#currentMutation?.state.status === "pending") { + this.#currentMutation.setOptions(this.options); + } + } + onUnsubscribe() { + if (!this.hasListeners()) { + this.#currentMutation?.removeObserver(this); + } + } + onMutationUpdate(action) { + this.#updateResult(); + this.#notify(action); + } + getCurrentResult() { + return this.#currentResult; + } + reset() { + this.#currentMutation?.removeObserver(this); + this.#currentMutation = void 0; + this.#updateResult(); + this.#notify(); + } + mutate(variables, options) { + this.#mutateOptions = options; + this.#currentMutation?.removeObserver(this); + this.#currentMutation = this.#client.getMutationCache().build(this.#client, this.options); + this.#currentMutation.addObserver(this); + return this.#currentMutation.execute(variables); + } + #updateResult() { + const state = this.#currentMutation?.state ?? getDefaultState(); + this.#currentResult = { + ...state, + isPending: state.status === "pending", + isSuccess: state.status === "success", + isError: state.status === "error", + isIdle: state.status === "idle", + mutate: this.mutate, + reset: this.reset, + }; + } + #notify(action) { + notifyManager.batch(() => { + if (this.#mutateOptions && this.hasListeners()) { + const variables = this.#currentResult.variables; + const onMutateResult = this.#currentResult.context; + const context = { + client: this.#client, + meta: this.options.meta, + mutationKey: this.options.mutationKey, + }; + if (action?.type === "success") { + try { + this.#mutateOptions.onSuccess?.(action.data, variables, onMutateResult, context); + } catch (e) { + void Promise.reject(e); + } + try { + this.#mutateOptions.onSettled?.(action.data, null, variables, onMutateResult, context); + } catch (e) { + void Promise.reject(e); + } + } else if (action?.type === "error") { + try { + this.#mutateOptions.onError?.(action.error, variables, onMutateResult, context); + } catch (e) { + void Promise.reject(e); + } + try { + this.#mutateOptions.onSettled?.(void 0, action.error, variables, onMutateResult, context); + } catch (e) { + void Promise.reject(e); + } + } + } + this.listeners.forEach((listener) => { + listener(this.#currentResult); + }); + }); + } +}; + +// Copyright 2021-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Returns a simplistic implementation for "structural sharing" for a Protobuf + * message. + * + * To keep references intact between re-renders, we return the old version if it + * equals the new version. + * + * See https://tanstack.com/query/latest/docs/framework/react/guides/render-optimizations#structural-sharing + */ +function createStructuralSharing(schema) { + return function (oldData, newData) { + if (!isMessage(oldData) || !isMessage(newData)) { + return replaceEqualDeep(oldData, newData); + } + if (!equals$1(schema, oldData, newData)) { + return newData; + } + return oldData; + }; +} + +// Copyright 2021-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// eslint-disable-next-line @typescript-eslint/max-params -- we have 4 required arguments +function createUnaryInfiniteQueryFn(transport, schema, input, { pageParamKey }) { + return async (context) => { + assert("pageParam" in context, "pageParam must be part of context"); + const inputCombinedWithPageParam = { + ...input, + [pageParamKey]: context.pageParam, + }; + return callUnaryMethod(transport, schema, inputCombinedWithPageParam, { + signal: context.signal, + headers: context.queryKey[1].headers, + }); + }; +} +function createInfiniteQueryOptions(schema, input, { transport, getNextPageParam, pageParamKey, headers }) { + const queryKey = createConnectQueryKey({ + cardinality: "infinite", + schema, + transport, + input, + pageParamKey, + headers, + }); + const structuralSharing = createStructuralSharing(schema.output); + const queryFn = + input === skipToken ? skipToken : ( + createUnaryInfiniteQueryFn(transport, schema, input, { + pageParamKey, + }) + ); + return { + getNextPageParam, + initialPageParam: input === skipToken ? undefined : input[pageParamKey], + queryKey, + queryFn, + structuralSharing, + }; +} + +// Copyright 2021-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +function createUnaryQueryFn(transport, schema, input) { + return async (context) => { + return callUnaryMethod(transport, schema, input, { + signal: context.signal, + headers: context.queryKey[1].headers, + }); + }; +} +function createQueryOptions(schema, input, { transport, headers }) { + const queryKey = createConnectQueryKey({ + schema, + input: input ?? create(schema.input), + transport, + cardinality: "finite", + headers, + }); + const structuralSharing = createStructuralSharing(schema.output); + const queryFn = input === skipToken ? skipToken : createUnaryQueryFn(transport, schema, input); + return { + queryKey, + queryFn, + structuralSharing, + }; +} + +// Copyright 2021-2026 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Connect represents categories of errors as codes, and each code maps to a + * specific HTTP status code. The codes and their semantics were chosen to + * match gRPC. Only the codes below are valid — there are no user-defined + * codes. + * + * See the specification at https://connectrpc.com/docs/protocol#error-codes + * for details. + */ +var Code; +(function (Code) { + /** + * Canceled, usually by the user + */ + Code[(Code["Canceled"] = 1)] = "Canceled"; + /** + * Unknown error + */ + Code[(Code["Unknown"] = 2)] = "Unknown"; + /** + * Argument invalid regardless of system state + */ + Code[(Code["InvalidArgument"] = 3)] = "InvalidArgument"; + /** + * Operation expired, may or may not have completed. + */ + Code[(Code["DeadlineExceeded"] = 4)] = "DeadlineExceeded"; + /** + * Entity not found. + */ + Code[(Code["NotFound"] = 5)] = "NotFound"; + /** + * Entity already exists. + */ + Code[(Code["AlreadyExists"] = 6)] = "AlreadyExists"; + /** + * Operation not authorized. + */ + Code[(Code["PermissionDenied"] = 7)] = "PermissionDenied"; + /** + * Quota exhausted. + */ + Code[(Code["ResourceExhausted"] = 8)] = "ResourceExhausted"; + /** + * Argument invalid in current system state. + */ + Code[(Code["FailedPrecondition"] = 9)] = "FailedPrecondition"; + /** + * Operation aborted. + */ + Code[(Code["Aborted"] = 10)] = "Aborted"; + /** + * Out of bounds, use instead of FailedPrecondition. + */ + Code[(Code["OutOfRange"] = 11)] = "OutOfRange"; + /** + * Operation not implemented or disabled. + */ + Code[(Code["Unimplemented"] = 12)] = "Unimplemented"; + /** + * Internal error, reserved for "serious errors". + */ + Code[(Code["Internal"] = 13)] = "Internal"; + /** + * Unavailable, client should back off and retry. + */ + Code[(Code["Unavailable"] = 14)] = "Unavailable"; + /** + * Unrecoverable data loss or corruption. + */ + Code[(Code["DataLoss"] = 15)] = "DataLoss"; + /** + * Request isn't authenticated. + */ + Code[(Code["Unauthenticated"] = 16)] = "Unauthenticated"; +})(Code || (Code = {})); + +// Copyright 2021-2026 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * codeToString returns the string representation of a Code. + * + * @private Internal code, does not follow semantic versioning. + */ +function codeToString(value) { + const name = Code[value]; + if (typeof name != "string") { + return value.toString(); + } + return name[0].toLowerCase() + name.substring(1).replace(/[A-Z]/g, (c) => "_" + c.toLowerCase()); +} + +// Copyright 2021-2026 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * ConnectError captures four pieces of information: a Code, an error + * message, an optional cause of the error, and an optional collection of + * arbitrary Protobuf messages called "details". + * + * Because developer tools typically show just the error message, we prefix + * it with the status code, so that the most important information is always + * visible immediately. + * + * Error details are wrapped with google.protobuf.Any on the wire, so that + * a server or middleware can attach arbitrary data to an error. Use the + * method findDetails() to retrieve the details. + */ +class ConnectError extends Error { + /** + * Create a new ConnectError. + * If no code is provided, code "unknown" is used. + * Outgoing details are only relevant for the server side - a service may + * raise an error with details, and it is up to the protocol implementation + * to encode and send the details along with the error. + */ + constructor(message, code = Code.Unknown, metadata, outgoingDetails, cause) { + super(createMessage(message, code)); + this.name = "ConnectError"; + // see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#example + Object.setPrototypeOf(this, new.target.prototype); + this.rawMessage = message; + this.code = code; + this.metadata = new Headers(metadata !== null && metadata !== void 0 ? metadata : {}); + this.details = outgoingDetails !== null && outgoingDetails !== void 0 ? outgoingDetails : []; + this.cause = cause; + } + /** + * Convert any value - typically a caught error into a ConnectError, + * following these rules: + * - If the value is already a ConnectError, return it as is. + * - If the value is an AbortError or TimeoutError from the fetch API, return + * the message of the error with code Canceled. + * - For other Errors, return the error message with code Unknown by default. + * - For other values, return the values String representation as a message, + * with the code Unknown by default. + * The original value will be used for the "cause" property for the new + * ConnectError. + */ + static from(reason, code = Code.Unknown) { + if (reason instanceof ConnectError) { + return reason; + } + if (reason instanceof Error) { + if (reason.name == "AbortError" || reason.name == "TimeoutError") { + // Fetch requests can only be canceled with an AbortController, + // or with AbortSignal.timeout(). + return new ConnectError(reason.message, Code.Canceled); + } + return new ConnectError(reason.message, code, undefined, undefined, reason); + } + return new ConnectError(String(reason), code, undefined, undefined, reason); + } + static [Symbol.hasInstance](v) { + if (!(v instanceof Error)) { + return false; + } + if (Object.getPrototypeOf(v) === ConnectError.prototype) { + return true; + } + return ( + v.name === "ConnectError" && + "code" in v && + typeof v.code === "number" && + "metadata" in v && + "details" in v && + Array.isArray(v.details) && + "rawMessage" in v && + typeof v.rawMessage == "string" && + "cause" in v + ); + } + findDetails(typeOrRegistry) { + const registry = + typeOrRegistry.kind === "message" ? + { + getMessage: (typeName) => (typeName === typeOrRegistry.typeName ? typeOrRegistry : undefined), + } + : typeOrRegistry; + const details = []; + for (const data of this.details) { + if ("desc" in data) { + if (registry.getMessage(data.desc.typeName)) { + details.push(create(data.desc, data.value)); + } + continue; + } + const desc = registry.getMessage(data.type); + if (desc) { + try { + details.push(fromBinary(desc, data.value)); + } catch (_) { + // We silently give up if we are unable to parse the detail, because + // that appears to be the least worst behavior. + // It is very unlikely that a user surrounds a catch body handling the + // error with another try-catch statement, and we do not want to + // recommend doing so. + } + } + } + return details; + } +} +/** + * Create an error message, prefixing the given code. + */ +function createMessage(message, code) { + return message.length ? `[${codeToString(code)}] ${message}` : `[${codeToString(code)}]`; +} + +// Copyright 2021-2026 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Create any client for the given service. + * + * The given createMethod function is called for each method definition + * of the service. The function it returns is added to the client object + * as a method. + */ +function makeAnyClient(service, createMethod) { + const client = {}; + for (const desc of service.methods) { + const method = createMethod(desc); + if (method != null) { + client[desc.localName] = method; + } + } + return client; +} + +// Copyright 2021-2026 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +var __asyncValues$1 = + (undefined && undefined.__asyncValues) || + function (o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator], + i; + return m ? + m.call(o) + : ((o = typeof __values === "function" ? __values(o) : o[Symbol.iterator]()), + (i = {}), + verb("next"), + verb("throw"), + verb("return"), + (i[Symbol.asyncIterator] = function () { + return this; + }), + i); + function verb(n) { + i[n] = + o[n] && + function (v) { + return new Promise(function (resolve, reject) { + ((v = o[n](v)), settle(resolve, reject, v.done, v.value)); + }); + }; + } + function settle(resolve, reject, d, v) { + Promise.resolve(v).then(function (v) { + resolve({ value: v, done: d }); + }, reject); + } + }; +var __await$1 = + (undefined && undefined.__await) || + function (v) { + return this instanceof __await$1 ? ((this.v = v), this) : new __await$1(v); + }; +var __asyncGenerator$1 = + (undefined && undefined.__asyncGenerator) || + function (thisArg, _arguments, generator) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var g = generator.apply(thisArg, _arguments || []), + i, + q = []; + return ( + (i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype)), + verb("next"), + verb("throw"), + verb("return", awaitReturn), + (i[Symbol.asyncIterator] = function () { + return this; + }), + i + ); + function awaitReturn(f) { + return function (v) { + return Promise.resolve(v).then(f, reject); + }; + } + function verb(n, f) { + if (g[n]) { + i[n] = function (v) { + return new Promise(function (a, b) { + q.push([n, v, a, b]) > 1 || resume(n, v); + }); + }; + if (f) i[n] = f(i[n]); + } + } + function resume(n, v) { + try { + step(g[n](v)); + } catch (e) { + settle(q[0][3], e); + } + } + function step(r) { + r.value instanceof __await$1 ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); + } + function fulfill(value) { + resume("next", value); + } + function reject(value) { + resume("throw", value); + } + function settle(f, v) { + if ((f(v), q.shift(), q.length)) resume(q[0][0], q[0][1]); + } + }; +var __asyncDelegator$1 = + (undefined && undefined.__asyncDelegator) || + function (o) { + var i, p; + return ( + (i = {}), + verb("next"), + verb("throw", function (e) { + throw e; + }), + verb("return"), + (i[Symbol.iterator] = function () { + return this; + }), + i + ); + function verb(n, f) { + i[n] = + o[n] ? + function (v) { + return ( + (p = !p) ? { value: __await$1(o[n](v)), done: false } + : f ? f(v) + : v + ); + } + : f; + } + }; +/** + * Create an asynchronous iterable from an array. + * + * @private Internal code, does not follow semantic versioning. + */ +function createAsyncIterable(items) { + return __asyncGenerator$1(this, arguments, function* createAsyncIterable_1() { + yield __await$1(yield* __asyncDelegator$1(__asyncValues$1(items))); + }); +} + +// Copyright 2021-2026 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +var __asyncValues = + (undefined && undefined.__asyncValues) || + function (o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator], + i; + return m ? + m.call(o) + : ((o = typeof __values === "function" ? __values(o) : o[Symbol.iterator]()), + (i = {}), + verb("next"), + verb("throw"), + verb("return"), + (i[Symbol.asyncIterator] = function () { + return this; + }), + i); + function verb(n) { + i[n] = + o[n] && + function (v) { + return new Promise(function (resolve, reject) { + ((v = o[n](v)), settle(resolve, reject, v.done, v.value)); + }); + }; + } + function settle(resolve, reject, d, v) { + Promise.resolve(v).then(function (v) { + resolve({ value: v, done: d }); + }, reject); + } + }; +var __await = + (undefined && undefined.__await) || + function (v) { + return this instanceof __await ? ((this.v = v), this) : new __await(v); + }; +var __asyncDelegator = + (undefined && undefined.__asyncDelegator) || + function (o) { + var i, p; + return ( + (i = {}), + verb("next"), + verb("throw", function (e) { + throw e; + }), + verb("return"), + (i[Symbol.iterator] = function () { + return this; + }), + i + ); + function verb(n, f) { + i[n] = + o[n] ? + function (v) { + return ( + (p = !p) ? { value: __await(o[n](v)), done: false } + : f ? f(v) + : v + ); + } + : f; + } + }; +var __asyncGenerator = + (undefined && undefined.__asyncGenerator) || + function (thisArg, _arguments, generator) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var g = generator.apply(thisArg, _arguments || []), + i, + q = []; + return ( + (i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype)), + verb("next"), + verb("throw"), + verb("return", awaitReturn), + (i[Symbol.asyncIterator] = function () { + return this; + }), + i + ); + function awaitReturn(f) { + return function (v) { + return Promise.resolve(v).then(f, reject); + }; + } + function verb(n, f) { + if (g[n]) { + i[n] = function (v) { + return new Promise(function (a, b) { + q.push([n, v, a, b]) > 1 || resume(n, v); + }); + }; + if (f) i[n] = f(i[n]); + } + } + function resume(n, v) { + try { + step(g[n](v)); + } catch (e) { + settle(q[0][3], e); + } + } + function step(r) { + r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); + } + function fulfill(value) { + resume("next", value); + } + function reject(value) { + resume("throw", value); + } + function settle(f, v) { + if ((f(v), q.shift(), q.length)) resume(q[0][0], q[0][1]); + } + }; +/** + * Create a Client for the given service, invoking RPCs through the + * given transport. + */ +function createClient(service, transport) { + return makeAnyClient(service, (method) => { + switch (method.methodKind) { + case "unary": + return createUnaryFn(transport, method); + case "server_streaming": + return createServerStreamingFn(transport, method); + case "client_streaming": + return createClientStreamingFn(transport, method); + case "bidi_streaming": + return createBiDiStreamingFn(transport, method); + default: + return null; + } + }); +} +function createUnaryFn(transport, method) { + return async (input, options) => { + var _a, _b; + const response = await transport.unary( + method, + options === null || options === void 0 ? void 0 : options.signal, + options === null || options === void 0 ? void 0 : options.timeoutMs, + options === null || options === void 0 ? void 0 : options.headers, + input, + options === null || options === void 0 ? void 0 : options.contextValues, + ); + (_a = options === null || options === void 0 ? void 0 : options.onHeader) === null || _a === void 0 ? void 0 : _a.call(options, response.header); + (_b = options === null || options === void 0 ? void 0 : options.onTrailer) === null || _b === void 0 ? void 0 : _b.call(options, response.trailer); + return response.message; + }; +} +function createServerStreamingFn(transport, method) { + return (input, options) => + handleStreamResponse( + transport.stream( + method, + options === null || options === void 0 ? void 0 : options.signal, + options === null || options === void 0 ? void 0 : options.timeoutMs, + options === null || options === void 0 ? void 0 : options.headers, + createAsyncIterable([input]), + options === null || options === void 0 ? void 0 : options.contextValues, + ), + options, + ); +} +function createClientStreamingFn(transport, method) { + return async (request, options) => { + var _a, e_1, _b, _c; + var _d, _e; + const response = await transport.stream( + method, + options === null || options === void 0 ? void 0 : options.signal, + options === null || options === void 0 ? void 0 : options.timeoutMs, + options === null || options === void 0 ? void 0 : options.headers, + request, + options === null || options === void 0 ? void 0 : options.contextValues, + ); + (_d = options === null || options === void 0 ? void 0 : options.onHeader) === null || _d === void 0 ? void 0 : _d.call(options, response.header); + let singleMessage; + let count = 0; + try { + for (var _f = true, _g = __asyncValues(response.message), _h; (_h = await _g.next()), (_a = _h.done), !_a; _f = true) { + _c = _h.value; + _f = false; + const message = _c; + singleMessage = message; + count++; + } + } catch (e_1_1) { + e_1 = { error: e_1_1 }; + } finally { + try { + if (!_f && !_a && (_b = _g.return)) await _b.call(_g); + } finally { + if (e_1) throw e_1.error; + } + } + if (!singleMessage) { + throw new ConnectError("protocol error: missing response message", Code.Unimplemented); + } + if (count > 1) { + throw new ConnectError("protocol error: received extra messages for client streaming method", Code.Unimplemented); + } + (_e = options === null || options === void 0 ? void 0 : options.onTrailer) === null || _e === void 0 ? void 0 : _e.call(options, response.trailer); + return singleMessage; + }; +} +function createBiDiStreamingFn(transport, method) { + return (request, options) => + handleStreamResponse( + transport.stream( + method, + options === null || options === void 0 ? void 0 : options.signal, + options === null || options === void 0 ? void 0 : options.timeoutMs, + options === null || options === void 0 ? void 0 : options.headers, + request, + options === null || options === void 0 ? void 0 : options.contextValues, + ), + options, + ); +} +function handleStreamResponse(stream, options) { + const it = (function () { + return __asyncGenerator(this, arguments, function* () { + var _a, _b; + const response = yield __await(stream); + (_a = options === null || options === void 0 ? void 0 : options.onHeader) === null || _a === void 0 ? void 0 : _a.call(options, response.header); + yield __await(yield* __asyncDelegator(__asyncValues(response.message))); + (_b = options === null || options === void 0 ? void 0 : options.onTrailer) === null || _b === void 0 ? void 0 : _b.call(options, response.trailer); + }); + })()[Symbol.asyncIterator](); + // Create a new iterable to omit throw/return. + return { + [Symbol.asyncIterator]: () => ({ + next: () => it.next(), + }), + }; +} + +// Copyright 2021-2026 Buf Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Hydrate a service descriptor. + * + * @private + */ +function serviceDesc(file, path, ...paths) { + if (paths.length > 0) { + throw new Error(); + } + return file.services[path]; +} + +const { createContext: createContext$2, useContext: useContext$2 } = await importShared("react"); + +const fallbackTransportError = new ConnectError( + "To use Connect, you must provide a `Transport`: a simple object that handles `unary` and `stream` requests. `Transport` objects can easily be created by using `@connectrpc/connect-web`'s exports `createConnectTransport` and `createGrpcWebTransport`. see: https://connectrpc.com/docs/web/getting-started for more info.", +); +// istanbul ignore next +const fallbackTransport = { + unary: () => { + throw fallbackTransportError; + }, + stream: () => { + throw fallbackTransportError; + }, +}; +const transportContext = createContext$2(fallbackTransport); +/** + * Use this helper to get the default transport that's currently attached to the React context for the calling component. + */ +const useTransport = () => useContext$2(transportContext); + +// src/QueryClientProvider.tsx +const React$9 = await importShared("react"); +var QueryClientContext = React$9.createContext(void 0); +var useQueryClient = (queryClient) => { + const client = React$9.useContext(QueryClientContext); + if (!client) { + throw new Error("No QueryClient set, use QueryClientProvider to set one"); + } + return client; +}; + +// src/IsRestoringProvider.ts +const React$8 = await importShared("react"); + +var IsRestoringContext = React$8.createContext(false); +var useIsRestoring = () => React$8.useContext(IsRestoringContext); +IsRestoringContext.Provider; + +// src/QueryErrorResetBoundary.tsx +const React$7 = await importShared("react"); +function createValue() { + let isReset = false; + return { + clearReset: () => { + isReset = false; + }, + reset: () => { + isReset = true; + }, + isReset: () => { + return isReset; + }, + }; +} +var QueryErrorResetBoundaryContext = React$7.createContext(createValue()); +var useQueryErrorResetBoundary = () => React$7.useContext(QueryErrorResetBoundaryContext); + +// src/errorBoundaryUtils.ts +const React$6 = await importShared("react"); +var ensurePreventErrorBoundaryRetry = (options, errorResetBoundary, query) => { + const throwOnError = query?.state.error && typeof options.throwOnError === "function" ? shouldThrowError(options.throwOnError, [query.state.error, query]) : options.throwOnError; + if (options.suspense || options.experimental_prefetchInRender || throwOnError) { + if (!errorResetBoundary.isReset()) { + options.retryOnMount = false; + } + } +}; +var useClearResetErrorBoundary = (errorResetBoundary) => { + React$6.useEffect(() => { + errorResetBoundary.clearReset(); + }, [errorResetBoundary]); +}; +var getHasError = ({ result, errorResetBoundary, throwOnError, query, suspense }) => { + return result.isError && !errorResetBoundary.isReset() && !result.isFetching && query && ((suspense && result.data === void 0) || shouldThrowError(throwOnError, [result.error, query])); +}; + +// src/suspense.ts +var ensureSuspenseTimers = (defaultedOptions) => { + if (defaultedOptions.suspense) { + const MIN_SUSPENSE_TIME_MS = 1e3; + const clamp = (value) => (value === "static" ? value : Math.max(value ?? MIN_SUSPENSE_TIME_MS, MIN_SUSPENSE_TIME_MS)); + const originalStaleTime = defaultedOptions.staleTime; + defaultedOptions.staleTime = typeof originalStaleTime === "function" ? (...args) => clamp(originalStaleTime(...args)) : clamp(originalStaleTime); + if (typeof defaultedOptions.gcTime === "number") { + defaultedOptions.gcTime = Math.max(defaultedOptions.gcTime, MIN_SUSPENSE_TIME_MS); + } + } +}; +var willFetch = (result, isRestoring) => result.isLoading && result.isFetching && !isRestoring; +var shouldSuspend = (defaultedOptions, result) => defaultedOptions?.suspense && result.isPending; +var fetchOptimistic = (defaultedOptions, observer, errorResetBoundary) => + observer.fetchOptimistic(defaultedOptions).catch(() => { + errorResetBoundary.clearReset(); + }); + +const React$5 = await importShared("react"); +function useBaseQuery(options, Observer, queryClient) { + const isRestoring = useIsRestoring(); + const errorResetBoundary = useQueryErrorResetBoundary(); + const client = useQueryClient(); + const defaultedOptions = client.defaultQueryOptions(options); + client.getDefaultOptions().queries?._experimental_beforeQuery?.(defaultedOptions); + const query = client.getQueryCache().get(defaultedOptions.queryHash); + const subscribed = options.subscribed !== false; + defaultedOptions._optimisticResults = + isRestoring ? "isRestoring" + : subscribed ? "optimistic" + : void 0; + ensureSuspenseTimers(defaultedOptions); + ensurePreventErrorBoundaryRetry(defaultedOptions, errorResetBoundary, query); + useClearResetErrorBoundary(errorResetBoundary); + const isNewCacheEntry = !client.getQueryCache().get(defaultedOptions.queryHash); + const [observer] = React$5.useState(() => new Observer(client, defaultedOptions)); + const result = observer.getOptimisticResult(defaultedOptions); + const shouldSubscribe = !isRestoring && subscribed; + React$5.useSyncExternalStore( + React$5.useCallback( + (onStoreChange) => { + const unsubscribe = shouldSubscribe ? observer.subscribe(notifyManager.batchCalls(onStoreChange)) : noop$1; + observer.updateResult(); + return unsubscribe; + }, + [observer, shouldSubscribe], + ), + () => observer.getCurrentResult(), + () => observer.getCurrentResult(), + ); + React$5.useEffect(() => { + observer.setOptions(defaultedOptions); + }, [defaultedOptions, observer]); + if (shouldSuspend(defaultedOptions, result)) { + throw fetchOptimistic(defaultedOptions, observer, errorResetBoundary); + } + if ( + getHasError({ + result, + errorResetBoundary, + throwOnError: defaultedOptions.throwOnError, + query, + suspense: defaultedOptions.suspense, + }) + ) { + throw result.error; + } + client.getDefaultOptions().queries?._experimental_afterQuery?.(defaultedOptions, result); + if (defaultedOptions.experimental_prefetchInRender && !environmentManager.isServer() && willFetch(result, isRestoring)) { + const promise = + isNewCacheEntry ? + // Fetch immediately on render in order to ensure `.promise` is resolved even if the component is unmounted + fetchOptimistic(defaultedOptions, observer, errorResetBoundary) + // subscribe to the "cache promise" so that we can finalize the currentThenable once data comes in + : query?.promise; + promise?.catch(noop$1).finally(() => { + observer.updateResult(); + }); + } + return !defaultedOptions.notifyOnChangeProps ? observer.trackResult(result) : result; +} + +function useQuery$1(options, queryClient) { + return useBaseQuery(options, QueryObserver); +} + +// src/useMutation.ts +const React$4 = await importShared("react"); +function useMutation$1(options, queryClient) { + const client = useQueryClient(); + const [observer] = React$4.useState(() => new MutationObserver(client, options)); + React$4.useEffect(() => { + observer.setOptions(options); + }, [observer, options]); + const result = React$4.useSyncExternalStore( + React$4.useCallback((onStoreChange) => observer.subscribe(notifyManager.batchCalls(onStoreChange)), [observer]), + () => observer.getCurrentResult(), + () => observer.getCurrentResult(), + ); + const mutate = React$4.useCallback( + (variables, mutateOptions) => { + observer.mutate(variables, mutateOptions).catch(noop$1); + }, + [observer], + ); + if (result.error && shouldThrowError(observer.options.throwOnError, [result.error])) { + throw result.error; + } + return { ...result, mutate, mutateAsync: result.mutate }; +} + +function useInfiniteQuery$1(options, queryClient) { + return useBaseQuery(options, InfiniteQueryObserver); +} + +// Copyright 2021-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Query the method provided. Maps to useInfiniteQuery on tanstack/react-query + */ +function useInfiniteQuery(schema, input, { transport, pageParamKey, getNextPageParam, ...queryOptions }) { + const transportFromCtx = useTransport(); + const baseOptions = createInfiniteQueryOptions(schema, input, { + transport: transport ?? transportFromCtx, + getNextPageParam, + pageParamKey, + }); + return useInfiniteQuery$1({ + ...baseOptions, + ...queryOptions, + }); +} + +// Copyright 2021-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/** + * Query the method provided. Maps to useQuery on tanstack/react-query + */ +function useQuery(schema, input, { transport, ...queryOptions } = {}) { + const transportFromCtx = useTransport(); + const baseOptions = createQueryOptions(schema, input, { + transport: transport ?? transportFromCtx, + }); + return useQuery$1({ + ...baseOptions, + ...queryOptions, + }); +} + +const { useCallback: useCallback$3 } = await importShared("react"); +/** + * Query the method provided. Maps to useMutation on tanstack/react-query + */ +function useMutation(schema, { transport, ...queryOptions } = {}) { + const transportFromCtx = useTransport(); + const transportToUse = transport ?? transportFromCtx; + const mutationFn = useCallback$3(async (input) => callUnaryMethod(transportToUse, schema, input), [transportToUse, schema]); + return useMutation$1({ + ...queryOptions, + mutationFn, + }); +} + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/common.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/common.proto. + */ +const file_blockninja_v1_common = /*@__PURE__*/ fileDesc( + "ChpibG9ja25pbmphL3YxL2NvbW1vbi5wcm90bxINYmxvY2tuaW5qYS52MSItCgpQYWdpbmF0aW9uEgwKBHBhZ2UYASABKAUSEQoJcGFnZV9zaXplGAIgASgFIlkKElBhZ2luYXRpb25SZXNwb25zZRINCgV0b3RhbBgBIAEoBRIMCgRwYWdlGAIgASgFEhEKCXBhZ2Vfc2l6ZRgDIAEoBRITCgt0b3RhbF9wYWdlcxgEIAEoBULBAQoRY29tLmJsb2NrbmluamEudjFCC0NvbW1vblByb3RvUAFaSmdpdC5kZXYuYWxleGR1bm1vdy5jb20vYmxvY2svbmluamEvaW50ZXJuYWwvYXBpL2Jsb2NrbmluamEvdjE7YmxvY2tuaW5qYXYxogIDQlhYqgINQmxvY2tuaW5qYS5WMcoCDUJsb2NrbmluamFcVjHiAhlCbG9ja25pbmphXFYxXEdQQk1ldGFkYXRh6gIOQmxvY2tuaW5qYTo6VjFiBnByb3RvMw", +); + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/auth.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/auth.proto. + */ +const file_blockninja_v1_auth = /*@__PURE__*/ fileDesc( + "ChhibG9ja25pbmphL3YxL2F1dGgucHJvdG8SDWJsb2NrbmluamEudjEiLwoMTG9naW5SZXF1ZXN0Eg0KBWVtYWlsGAEgASgJEhAKCHBhc3N3b3JkGAIgASgJIjcKDUxvZ2luUmVzcG9uc2USJgoEdXNlchgBIAEoCzIYLmJsb2NrbmluamEudjEuQWRtaW5Vc2VyIg8KDUxvZ291dFJlcXVlc3QiEAoOTG9nb3V0UmVzcG9uc2UiFwoVUmVmcmVzaFNlc3Npb25SZXF1ZXN0IkAKFlJlZnJlc2hTZXNzaW9uUmVzcG9uc2USJgoEdXNlchgBIAEoCzIYLmJsb2NrbmluamEudjEuQWRtaW5Vc2VyIhcKFUdldEN1cnJlbnRVc2VyUmVxdWVzdCJAChZHZXRDdXJyZW50VXNlclJlc3BvbnNlEiYKBHVzZXIYASABKAsyGC5ibG9ja25pbmphLnYxLkFkbWluVXNlciIeChxMaXN0QXV0aG9yaXplZERldmljZXNSZXF1ZXN0IlEKHUxpc3RBdXRob3JpemVkRGV2aWNlc1Jlc3BvbnNlEjAKB2RldmljZXMYASADKAsyHy5ibG9ja25pbmphLnYxLkF1dGhvcml6ZWREZXZpY2UiOQodUmV2b2tlQXV0aG9yaXplZERldmljZVJlcXVlc3QSGAoQYXV0aG9yaXphdGlvbl9pZBgBIAEoCSIgCh5SZXZva2VBdXRob3JpemVkRGV2aWNlUmVzcG9uc2UirAIKEEF1dGhvcml6ZWREZXZpY2USCgoCaWQYASABKAkSEQoJY2xpZW50X2lkGAIgASgJEhcKCnVzZXJfYWdlbnQYAyABKAlIAIgBARIXCgppcF9hZGRyZXNzGAQgASgJSAGIAQESMQoNYXV0aG9yaXplZF9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKZXhwaXJlc19hdBgGIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASNQoMbGFzdF9zZWVuX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgCiAEBQg0KC191c2VyX2FnZW50Qg0KC19pcF9hZGRyZXNzQg8KDV9sYXN0X3NlZW5fYXQiRwoVQ2hhbmdlUGFzc3dvcmRSZXF1ZXN0EhgKEGN1cnJlbnRfcGFzc3dvcmQYASABKAkSFAoMbmV3X3Bhc3N3b3JkGAIgASgJIhgKFkNoYW5nZVBhc3N3b3JkUmVzcG9uc2UiLwoXU2V0TG9jYWxQYXNzd29yZFJlcXVlc3QSFAoMbmV3X3Bhc3N3b3JkGAEgASgJIhoKGFNldExvY2FsUGFzc3dvcmRSZXNwb25zZSIsChtSZXF1ZXN0UGFzc3dvcmRSZXNldFJlcXVlc3QSDQoFZW1haWwYASABKAkiHgocUmVxdWVzdFBhc3N3b3JkUmVzZXRSZXNwb25zZSI7ChRSZXNldFBhc3N3b3JkUmVxdWVzdBINCgV0b2tlbhgBIAEoCRIUCgxuZXdfcGFzc3dvcmQYAiABKAkiFwoVUmVzZXRQYXNzd29yZFJlc3BvbnNlIpIECglBZG1pblVzZXISCgoCaWQYASABKAkSDQoFZW1haWwYAiABKAkSIQoEcm9sZRgDIAEoDjITLmJsb2NrbmluamEudjEuUm9sZRISCgpmaXJzdF9uYW1lGAQgASgJEhEKCWxhc3RfbmFtZRgFIAEoCRIRCglpc19hY3RpdmUYBiABKAgSNgoNbGFzdF9sb2dpbl9hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIAIgBARIuCgpjcmVhdGVkX2F0GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAkgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIPCgdoYXNfc3NvGAogASgIEhoKEmhhc19sb2NhbF9wYXNzd29yZBgLIAEoCBISCgppc19wZW5kaW5nGAwgASgIEjMKCmludml0ZWRfYXQYDSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSAGIAQESGgoNaW52aXRlZF9ieV9pZBgOIAEoCUgCiAEBEhwKD2ludml0ZWRfYnlfbmFtZRgPIAEoCUgDiAEBQhAKDl9sYXN0X2xvZ2luX2F0Qg0KC19pbnZpdGVkX2F0QhAKDl9pbnZpdGVkX2J5X2lkQhIKEF9pbnZpdGVkX2J5X25hbWUqUgoEUm9sZRIUChBST0xFX1VOU1BFQ0lGSUVEEAASDgoKUk9MRV9BRE1JThABEhMKD1JPTEVfU1VQRVJBRE1JThACEg8KC1JPTEVfVklFV0VSEAMy0gcKC0F1dGhTZXJ2aWNlEkIKBUxvZ2luEhsuYmxvY2tuaW5qYS52MS5Mb2dpblJlcXVlc3QaHC5ibG9ja25pbmphLnYxLkxvZ2luUmVzcG9uc2USRQoGTG9nb3V0EhwuYmxvY2tuaW5qYS52MS5Mb2dvdXRSZXF1ZXN0Gh0uYmxvY2tuaW5qYS52MS5Mb2dvdXRSZXNwb25zZRJdCg5SZWZyZXNoU2Vzc2lvbhIkLmJsb2NrbmluamEudjEuUmVmcmVzaFNlc3Npb25SZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5SZWZyZXNoU2Vzc2lvblJlc3BvbnNlEl0KDkdldEN1cnJlbnRVc2VyEiQuYmxvY2tuaW5qYS52MS5HZXRDdXJyZW50VXNlclJlcXVlc3QaJS5ibG9ja25pbmphLnYxLkdldEN1cnJlbnRVc2VyUmVzcG9uc2UScgoVTGlzdEF1dGhvcml6ZWREZXZpY2VzEisuYmxvY2tuaW5qYS52MS5MaXN0QXV0aG9yaXplZERldmljZXNSZXF1ZXN0GiwuYmxvY2tuaW5qYS52MS5MaXN0QXV0aG9yaXplZERldmljZXNSZXNwb25zZRJ1ChZSZXZva2VBdXRob3JpemVkRGV2aWNlEiwuYmxvY2tuaW5qYS52MS5SZXZva2VBdXRob3JpemVkRGV2aWNlUmVxdWVzdBotLmJsb2NrbmluamEudjEuUmV2b2tlQXV0aG9yaXplZERldmljZVJlc3BvbnNlEl0KDkNoYW5nZVBhc3N3b3JkEiQuYmxvY2tuaW5qYS52MS5DaGFuZ2VQYXNzd29yZFJlcXVlc3QaJS5ibG9ja25pbmphLnYxLkNoYW5nZVBhc3N3b3JkUmVzcG9uc2USYwoQU2V0TG9jYWxQYXNzd29yZBImLmJsb2NrbmluamEudjEuU2V0TG9jYWxQYXNzd29yZFJlcXVlc3QaJy5ibG9ja25pbmphLnYxLlNldExvY2FsUGFzc3dvcmRSZXNwb25zZRJvChRSZXF1ZXN0UGFzc3dvcmRSZXNldBIqLmJsb2NrbmluamEudjEuUmVxdWVzdFBhc3N3b3JkUmVzZXRSZXF1ZXN0GisuYmxvY2tuaW5qYS52MS5SZXF1ZXN0UGFzc3dvcmRSZXNldFJlc3BvbnNlEloKDVJlc2V0UGFzc3dvcmQSIy5ibG9ja25pbmphLnYxLlJlc2V0UGFzc3dvcmRSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5SZXNldFBhc3N3b3JkUmVzcG9uc2VCvwEKEWNvbS5ibG9ja25pbmphLnYxQglBdXRoUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_blockninja_v1_common, file_google_protobuf_timestamp], +); +/** + * @generated from enum blockninja.v1.Role + */ +var Role; +(function (Role) { + /** + * @generated from enum value: ROLE_UNSPECIFIED = 0; + */ + Role[(Role["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * @generated from enum value: ROLE_ADMIN = 1; + */ + Role[(Role["ADMIN"] = 1)] = "ADMIN"; + /** + * @generated from enum value: ROLE_SUPERADMIN = 2; + */ + Role[(Role["SUPERADMIN"] = 2)] = "SUPERADMIN"; + /** + * @generated from enum value: ROLE_VIEWER = 3; + */ + Role[(Role["VIEWER"] = 3)] = "VIEWER"; +})(Role || (Role = {})); +/** + * @generated from service blockninja.v1.AuthService + */ +const AuthService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_auth, 0); + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/admin.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/admin.proto. + */ +const file_blockninja_v1_admin = /*@__PURE__*/ fileDesc( + "ChlibG9ja25pbmphL3YxL2FkbWluLnByb3RvEg1ibG9ja25pbmphLnYxItkBChVMaXN0QWRtaW5Vc2Vyc1JlcXVlc3QSLQoKcGFnaW5hdGlvbhgBIAEoCzIZLmJsb2NrbmluamEudjEuUGFnaW5hdGlvbhItCgtyb2xlX2ZpbHRlchgCIAEoDjITLmJsb2NrbmluamEudjEuUm9sZUgAiAEBEh0KEGlzX2FjdGl2ZV9maWx0ZXIYAyABKAhIAYgBARITCgZzZWFyY2gYBCABKAlIAogBAUIOCgxfcm9sZV9maWx0ZXJCEwoRX2lzX2FjdGl2ZV9maWx0ZXJCCQoHX3NlYXJjaCJ4ChZMaXN0QWRtaW5Vc2Vyc1Jlc3BvbnNlEicKBXVzZXJzGAEgAygLMhguYmxvY2tuaW5qYS52MS5BZG1pblVzZXISNQoKcGFnaW5hdGlvbhgCIAEoCzIhLmJsb2NrbmluamEudjEuUGFnaW5hdGlvblJlc3BvbnNlIiEKE0dldEFkbWluVXNlclJlcXVlc3QSCgoCaWQYASABKAkiPgoUR2V0QWRtaW5Vc2VyUmVzcG9uc2USJgoEdXNlchgBIAEoCzIYLmJsb2NrbmluamEudjEuQWRtaW5Vc2VyInEKFkludml0ZUFkbWluVXNlclJlcXVlc3QSDQoFZW1haWwYASABKAkSIQoEcm9sZRgCIAEoDjITLmJsb2NrbmluamEudjEuUm9sZRISCgpmaXJzdF9uYW1lGAMgASgJEhEKCWxhc3RfbmFtZRgEIAEoCSJVChdJbnZpdGVBZG1pblVzZXJSZXNwb25zZRImCgR1c2VyGAEgASgLMhguYmxvY2tuaW5qYS52MS5BZG1pblVzZXISEgoKZW1haWxfc2VudBgCIAEoCCImChVWYWxpZGF0ZUludml0ZVJlcXVlc3QSDQoFdG9rZW4YASABKAkilAEKFlZhbGlkYXRlSW52aXRlUmVzcG9uc2USDQoFdmFsaWQYASABKAgSDQoFZW1haWwYAiABKAkSEgoKZmlyc3RfbmFtZRgDIAEoCRIRCglsYXN0X25hbWUYBCABKAkSIQoEcm9sZRgFIAEoDjITLmJsb2NrbmluamEudjEuUm9sZRISCgpleHBpcmVzX2F0GAYgASgJIjYKE0FjY2VwdEludml0ZVJlcXVlc3QSDQoFdG9rZW4YASABKAkSEAoIcGFzc3dvcmQYAiABKAkiPgoUQWNjZXB0SW52aXRlUmVzcG9uc2USJgoEdXNlchgBIAEoCzIYLmJsb2NrbmluamEudjEuQWRtaW5Vc2VyIiYKE1Jlc2VuZEludml0ZVJlcXVlc3QSDwoHdXNlcl9pZBgBIAEoCSI7ChRSZXNlbmRJbnZpdGVSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEhIKCmVtYWlsX3NlbnQYAiABKAgiJgoTQ2FuY2VsSW52aXRlUmVxdWVzdBIPCgd1c2VyX2lkGAEgASgJIicKFENhbmNlbEludml0ZVJlc3BvbnNlEg8KB3N1Y2Nlc3MYASABKAgi5wEKFlVwZGF0ZUFkbWluVXNlclJlcXVlc3QSCgoCaWQYASABKAkSEgoFZW1haWwYAiABKAlIAIgBARImCgRyb2xlGAMgASgOMhMuYmxvY2tuaW5qYS52MS5Sb2xlSAGIAQESFwoKZmlyc3RfbmFtZRgEIAEoCUgCiAEBEhYKCWxhc3RfbmFtZRgFIAEoCUgDiAEBEhYKCWlzX2FjdGl2ZRgGIAEoCEgEiAEBQggKBl9lbWFpbEIHCgVfcm9sZUINCgtfZmlyc3RfbmFtZUIMCgpfbGFzdF9uYW1lQgwKCl9pc19hY3RpdmUiQQoXVXBkYXRlQWRtaW5Vc2VyUmVzcG9uc2USJgoEdXNlchgBIAEoCzIYLmJsb2NrbmluamEudjEuQWRtaW5Vc2VyIjcKFkRlbGV0ZUFkbWluVXNlclJlcXVlc3QSCgoCaWQYASABKAkSEQoJcGVybWFuZW50GAIgASgIIhkKF0RlbGV0ZUFkbWluVXNlclJlc3BvbnNlImQKFFVwZGF0ZVByb2ZpbGVSZXF1ZXN0EhcKCmZpcnN0X25hbWUYASABKAlIAIgBARIWCglsYXN0X25hbWUYAiABKAlIAYgBAUINCgtfZmlyc3RfbmFtZUIMCgpfbGFzdF9uYW1lIj8KFVVwZGF0ZVByb2ZpbGVSZXNwb25zZRImCgR1c2VyGAEgASgLMhguYmxvY2tuaW5qYS52MS5BZG1pblVzZXIysgcKDEFkbWluU2VydmljZRJdCg5MaXN0QWRtaW5Vc2VycxIkLmJsb2NrbmluamEudjEuTGlzdEFkbWluVXNlcnNSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5MaXN0QWRtaW5Vc2Vyc1Jlc3BvbnNlElcKDEdldEFkbWluVXNlchIiLmJsb2NrbmluamEudjEuR2V0QWRtaW5Vc2VyUmVxdWVzdBojLmJsb2NrbmluamEudjEuR2V0QWRtaW5Vc2VyUmVzcG9uc2USYAoPVXBkYXRlQWRtaW5Vc2VyEiUuYmxvY2tuaW5qYS52MS5VcGRhdGVBZG1pblVzZXJSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5VcGRhdGVBZG1pblVzZXJSZXNwb25zZRJgCg9EZWxldGVBZG1pblVzZXISJS5ibG9ja25pbmphLnYxLkRlbGV0ZUFkbWluVXNlclJlcXVlc3QaJi5ibG9ja25pbmphLnYxLkRlbGV0ZUFkbWluVXNlclJlc3BvbnNlEloKDVVwZGF0ZVByb2ZpbGUSIy5ibG9ja25pbmphLnYxLlVwZGF0ZVByb2ZpbGVSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5VcGRhdGVQcm9maWxlUmVzcG9uc2USYAoPSW52aXRlQWRtaW5Vc2VyEiUuYmxvY2tuaW5qYS52MS5JbnZpdGVBZG1pblVzZXJSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5JbnZpdGVBZG1pblVzZXJSZXNwb25zZRJdCg5WYWxpZGF0ZUludml0ZRIkLmJsb2NrbmluamEudjEuVmFsaWRhdGVJbnZpdGVSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5WYWxpZGF0ZUludml0ZVJlc3BvbnNlElcKDEFjY2VwdEludml0ZRIiLmJsb2NrbmluamEudjEuQWNjZXB0SW52aXRlUmVxdWVzdBojLmJsb2NrbmluamEudjEuQWNjZXB0SW52aXRlUmVzcG9uc2USVwoMUmVzZW5kSW52aXRlEiIuYmxvY2tuaW5qYS52MS5SZXNlbmRJbnZpdGVSZXF1ZXN0GiMuYmxvY2tuaW5qYS52MS5SZXNlbmRJbnZpdGVSZXNwb25zZRJXCgxDYW5jZWxJbnZpdGUSIi5ibG9ja25pbmphLnYxLkNhbmNlbEludml0ZVJlcXVlc3QaIy5ibG9ja25pbmphLnYxLkNhbmNlbEludml0ZVJlc3BvbnNlQsABChFjb20uYmxvY2tuaW5qYS52MUIKQWRtaW5Qcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_blockninja_v1_common, file_blockninja_v1_auth], +); +/** + * @generated from service blockninja.v1.AdminService + */ +const AdminService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_admin, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/admin.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * @generated from rpc blockninja.v1.AdminService.ListAdminUsers + */ +AdminService.method.listAdminUsers; +/** + * @generated from rpc blockninja.v1.AdminService.GetAdminUser + */ +AdminService.method.getAdminUser; +/** + * @generated from rpc blockninja.v1.AdminService.UpdateAdminUser + */ +AdminService.method.updateAdminUser; +/** + * @generated from rpc blockninja.v1.AdminService.DeleteAdminUser + */ +AdminService.method.deleteAdminUser; +/** + * @generated from rpc blockninja.v1.AdminService.UpdateProfile + */ +AdminService.method.updateProfile; +/** + * Invite-only workflow (replaces CreateAdminUser) + * + * @generated from rpc blockninja.v1.AdminService.InviteAdminUser + */ +AdminService.method.inviteAdminUser; +/** + * @generated from rpc blockninja.v1.AdminService.ValidateInvite + */ +AdminService.method.validateInvite; +/** + * @generated from rpc blockninja.v1.AdminService.AcceptInvite + */ +AdminService.method.acceptInvite; +/** + * @generated from rpc blockninja.v1.AdminService.ResendInvite + */ +AdminService.method.resendInvite; +/** + * @generated from rpc blockninja.v1.AdminService.CancelInvite + */ +AdminService.method.cancelInvite; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/ai.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/ai.proto. + */ +const file_blockninja_v1_ai = /*@__PURE__*/ fileDesc( + "ChZibG9ja25pbmphL3YxL2FpLnByb3RvEg1ibG9ja25pbmphLnYxIpICCgVBSUtleRIKCgJpZBgBIAEoCRIrCghwcm92aWRlchgCIAEoDjIZLmJsb2NrbmluamEudjEuQUlQcm92aWRlchISCgptYXNrZWRfa2V5GAMgASgJEhAKCGlzX3ZhbGlkGAQgASgIEhgKEHZhbGlkYXRpb25fZXJyb3IYBSABKAkSMAoMdmFsaWRhdGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgpjcmVhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCJ7CgdBSU1vZGVsEgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSKwoIcHJvdmlkZXIYBCABKA4yGS5ibG9ja25pbmphLnYxLkFJUHJvdmlkZXISFAoMaXNfYXZhaWxhYmxlGAUgASgIIpkBCgpBSVNldHRpbmdzEjIKD2FjdGl2ZV9wcm92aWRlchgBIAEoDjIZLmJsb2NrbmluamEudjEuQUlQcm92aWRlchIUCgxhY3RpdmVfbW9kZWwYAiABKAkSEgoKYWlfZW5hYmxlZBgDIAEoCBIUCgxvcGVuYWlfbW9kZWwYBCABKAkSFwoPYW50aHJvcGljX21vZGVsGAUgASgJIhMKEUxpc3RBSUtleXNSZXF1ZXN0IjgKEkxpc3RBSUtleXNSZXNwb25zZRIiCgRrZXlzGAEgAygLMhQuYmxvY2tuaW5qYS52MS5BSUtleSJhCg9TZXRBSUtleVJlcXVlc3QSKwoIcHJvdmlkZXIYASABKA4yGS5ibG9ja25pbmphLnYxLkFJUHJvdmlkZXISDwoHYXBpX2tleRgCIAEoCRIQCgh0ZXN0X2tleRgDIAEoCCI1ChBTZXRBSUtleVJlc3BvbnNlEiEKA2tleRgBIAEoCzIULmJsb2NrbmluamEudjEuQUlLZXkiIAoSRGVsZXRlQUlLZXlSZXF1ZXN0EgoKAmlkGAEgASgJIiYKE0RlbGV0ZUFJS2V5UmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCJQChBUZXN0QUlLZXlSZXF1ZXN0EisKCHByb3ZpZGVyGAEgASgOMhkuYmxvY2tuaW5qYS52MS5BSVByb3ZpZGVyEg8KB2FwaV9rZXkYAiABKAkiZgoRVGVzdEFJS2V5UmVzcG9uc2USEAoIaXNfdmFsaWQYASABKAgSDQoFZXJyb3IYAiABKAkSMAoQYXZhaWxhYmxlX21vZGVscxgDIAMoCzIWLmJsb2NrbmluamEudjEuQUlNb2RlbCJCChNMaXN0QUlNb2RlbHNSZXF1ZXN0EisKCHByb3ZpZGVyGAEgASgOMhkuYmxvY2tuaW5qYS52MS5BSVByb3ZpZGVyInAKFExpc3RBSU1vZGVsc1Jlc3BvbnNlEiYKBm1vZGVscxgBIAMoCzIWLmJsb2NrbmluamEudjEuQUlNb2RlbBIwCgxsYXN0X2ZldGNoZWQYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIkUKFlJlZnJlc2hBSU1vZGVsc1JlcXVlc3QSKwoIcHJvdmlkZXIYASABKA4yGS5ibG9ja25pbmphLnYxLkFJUHJvdmlkZXIicQoXUmVmcmVzaEFJTW9kZWxzUmVzcG9uc2USJgoGbW9kZWxzGAEgAygLMhYuYmxvY2tuaW5qYS52MS5BSU1vZGVsEi4KCmZldGNoZWRfYXQYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIhYKFEdldEFJU2V0dGluZ3NSZXF1ZXN0IkQKFUdldEFJU2V0dGluZ3NSZXNwb25zZRIrCghzZXR0aW5ncxgBIAEoCzIZLmJsb2NrbmluamEudjEuQUlTZXR0aW5ncyKYAgoXVXBkYXRlQUlTZXR0aW5nc1JlcXVlc3QSNwoPYWN0aXZlX3Byb3ZpZGVyGAEgASgOMhkuYmxvY2tuaW5qYS52MS5BSVByb3ZpZGVySACIAQESGQoMYWN0aXZlX21vZGVsGAIgASgJSAGIAQESFwoKYWlfZW5hYmxlZBgDIAEoCEgCiAEBEhkKDG9wZW5haV9tb2RlbBgEIAEoCUgDiAEBEhwKD2FudGhyb3BpY19tb2RlbBgFIAEoCUgEiAEBQhIKEF9hY3RpdmVfcHJvdmlkZXJCDwoNX2FjdGl2ZV9tb2RlbEINCgtfYWlfZW5hYmxlZEIPCg1fb3BlbmFpX21vZGVsQhIKEF9hbnRocm9waWNfbW9kZWwiRwoYVXBkYXRlQUlTZXR0aW5nc1Jlc3BvbnNlEisKCHNldHRpbmdzGAEgASgLMhkuYmxvY2tuaW5qYS52MS5BSVNldHRpbmdzIoIDCgxBSVRhc2tDb25maWcSCgoCaWQYASABKAkSIwoEdGFzaxgCIAEoDjIVLmJsb2NrbmluamEudjEuQUlUYXNrEisKCHByb3ZpZGVyGAMgASgOMhkuYmxvY2tuaW5qYS52MS5BSVByb3ZpZGVyEg0KBW1vZGVsGAQgASgJEhgKC3RlbXBlcmF0dXJlGAUgASgCSACIAQESFwoKbWF4X3Rva2VucxgGIAEoBUgBiAEBEhoKDXN5c3RlbV9wcm9tcHQYByABKAlIAogBARIuCgpjcmVhdGVkX2F0GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAkgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBITCgtwbHVnaW5fbmFtZRgKIAEoCRIQCgh0YXNrX2tleRgLIAEoCUIOCgxfdGVtcGVyYXR1cmVCDQoLX21heF90b2tlbnNCEAoOX3N5c3RlbV9wcm9tcHQiPQoWR2V0QUlUYXNrQ29uZmlnUmVxdWVzdBIjCgR0YXNrGAEgASgOMhUuYmxvY2tuaW5qYS52MS5BSVRhc2siVgoXR2V0QUlUYXNrQ29uZmlnUmVzcG9uc2USMAoGY29uZmlnGAEgASgLMhsuYmxvY2tuaW5qYS52MS5BSVRhc2tDb25maWdIAIgBAUIJCgdfY29uZmlnIhoKGExpc3RBSVRhc2tDb25maWdzUmVxdWVzdCJJChlMaXN0QUlUYXNrQ29uZmlnc1Jlc3BvbnNlEiwKB2NvbmZpZ3MYASADKAsyGy5ibG9ja25pbmphLnYxLkFJVGFza0NvbmZpZyKLAgoWU2V0QUlUYXNrQ29uZmlnUmVxdWVzdBIjCgR0YXNrGAEgASgOMhUuYmxvY2tuaW5qYS52MS5BSVRhc2sSKwoIcHJvdmlkZXIYAiABKA4yGS5ibG9ja25pbmphLnYxLkFJUHJvdmlkZXISDQoFbW9kZWwYAyABKAkSGAoLdGVtcGVyYXR1cmUYBCABKAJIAIgBARIXCgptYXhfdG9rZW5zGAUgASgFSAGIAQESGgoNc3lzdGVtX3Byb21wdBgGIAEoCUgCiAEBEhAKCHRhc2tfa2V5GAcgASgJQg4KDF90ZW1wZXJhdHVyZUINCgtfbWF4X3Rva2Vuc0IQCg5fc3lzdGVtX3Byb21wdCJGChdTZXRBSVRhc2tDb25maWdSZXNwb25zZRIrCgZjb25maWcYASABKAsyGy5ibG9ja25pbmphLnYxLkFJVGFza0NvbmZpZyJSChlEZWxldGVBSVRhc2tDb25maWdSZXF1ZXN0EiMKBHRhc2sYASABKA4yFS5ibG9ja25pbmphLnYxLkFJVGFzaxIQCgh0YXNrX2tleRgCIAEoCSItChpEZWxldGVBSVRhc2tDb25maWdSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIIqkBChZHZW5lcmF0ZVNFT01ldGFSZXF1ZXN0Eg8KB3BhZ2VfaWQYASABKAkSDwoHY29udGVudBgCIAEoCRISCgpwYWdlX3RpdGxlGAMgASgJEhoKDWZvY3VzX2tleXdvcmQYBCABKAlIAIgBARIrCgZmaWVsZHMYBSADKA4yGy5ibG9ja25pbmphLnYxLlNFT0ZpZWxkVHlwZUIQCg5fZm9jdXNfa2V5d29yZCLJAQoXR2VuZXJhdGVTRU9NZXRhUmVzcG9uc2USFwoKbWV0YV90aXRsZRgBIAEoCUgAiAEBEh0KEG1ldGFfZGVzY3JpcHRpb24YAiABKAlIAYgBARIVCghvZ190aXRsZRgDIAEoCUgCiAEBEhsKDm9nX2Rlc2NyaXB0aW9uGAQgASgJSAOIAQFCDQoLX21ldGFfdGl0bGVCEwoRX21ldGFfZGVzY3JpcHRpb25CCwoJX29nX3RpdGxlQhEKD19vZ19kZXNjcmlwdGlvbiKHAQoOUGx1Z2luQUlBY3Rpb24SCwoDa2V5GAEgASgJEg0KBXRpdGxlGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEhMKC3BsdWdpbl9uYW1lGAQgASgJEhgKEGRlZmF1bHRfcHJvdmlkZXIYBSABKAkSFQoNZGVmYXVsdF9tb2RlbBgGIAEoCSIgCh5MaXN0UmVnaXN0ZXJlZEFJQWN0aW9uc1JlcXVlc3QiUQofTGlzdFJlZ2lzdGVyZWRBSUFjdGlvbnNSZXNwb25zZRIuCgdhY3Rpb25zGAEgAygLMh0uYmxvY2tuaW5qYS52MS5QbHVnaW5BSUFjdGlvbipcCgpBSVByb3ZpZGVyEhsKF0FJX1BST1ZJREVSX1VOU1BFQ0lGSUVEEAASFgoSQUlfUFJPVklERVJfT1BFTkFJEAESGQoVQUlfUFJPVklERVJfQU5USFJPUElDEAIqnQIKBkFJVGFzaxIXChNBSV9UQVNLX1VOU1BFQ0lGSUVEEAASGgoWQUlfVEFTS19JTUFHRV9BTFRfVEVYVBABEhYKEkFJX1RBU0tfSU1BR0VfVEFHUxACEh8KG0FJX1RBU0tfQ09OVEVOVF9TVUdHRVNUSU9OUxADEhwKGEFJX1RBU0tfTUVUQV9ERVNDUklQVElPThAEEhcKE0FJX1RBU0tfU01BUlRfQkxPQ0sQBRIeChpBSV9UQVNLX1NNQVJUX0JMT0NLX0FTU0lTVBAGEh8KG0FJX1RBU0tfU0lURV9TVEFUVVNfTUVTU0FHRRAHEhcKE0FJX1RBU0tfQkxPQ0tfVElUTEUQCBIUChBBSV9UQVNLX1NFT19NRVRBEAkqsgEKDFNFT0ZpZWxkVHlwZRIeChpTRU9fRklFTERfVFlQRV9VTlNQRUNJRklFRBAAEh0KGVNFT19GSUVMRF9UWVBFX01FVEFfVElUTEUQARIjCh9TRU9fRklFTERfVFlQRV9NRVRBX0RFU0NSSVBUSU9OEAISGwoXU0VPX0ZJRUxEX1RZUEVfT0dfVElUTEUQAxIhCh1TRU9fRklFTERfVFlQRV9PR19ERVNDUklQVElPThAEMsAKCglBSVNlcnZpY2USUQoKTGlzdEFJS2V5cxIgLmJsb2NrbmluamEudjEuTGlzdEFJS2V5c1JlcXVlc3QaIS5ibG9ja25pbmphLnYxLkxpc3RBSUtleXNSZXNwb25zZRJLCghTZXRBSUtleRIeLmJsb2NrbmluamEudjEuU2V0QUlLZXlSZXF1ZXN0Gh8uYmxvY2tuaW5qYS52MS5TZXRBSUtleVJlc3BvbnNlElQKC0RlbGV0ZUFJS2V5EiEuYmxvY2tuaW5qYS52MS5EZWxldGVBSUtleVJlcXVlc3QaIi5ibG9ja25pbmphLnYxLkRlbGV0ZUFJS2V5UmVzcG9uc2USTgoJVGVzdEFJS2V5Eh8uYmxvY2tuaW5qYS52MS5UZXN0QUlLZXlSZXF1ZXN0GiAuYmxvY2tuaW5qYS52MS5UZXN0QUlLZXlSZXNwb25zZRJXCgxMaXN0QUlNb2RlbHMSIi5ibG9ja25pbmphLnYxLkxpc3RBSU1vZGVsc1JlcXVlc3QaIy5ibG9ja25pbmphLnYxLkxpc3RBSU1vZGVsc1Jlc3BvbnNlEmAKD1JlZnJlc2hBSU1vZGVscxIlLmJsb2NrbmluamEudjEuUmVmcmVzaEFJTW9kZWxzUmVxdWVzdBomLmJsb2NrbmluamEudjEuUmVmcmVzaEFJTW9kZWxzUmVzcG9uc2USWgoNR2V0QUlTZXR0aW5ncxIjLmJsb2NrbmluamEudjEuR2V0QUlTZXR0aW5nc1JlcXVlc3QaJC5ibG9ja25pbmphLnYxLkdldEFJU2V0dGluZ3NSZXNwb25zZRJjChBVcGRhdGVBSVNldHRpbmdzEiYuYmxvY2tuaW5qYS52MS5VcGRhdGVBSVNldHRpbmdzUmVxdWVzdBonLmJsb2NrbmluamEudjEuVXBkYXRlQUlTZXR0aW5nc1Jlc3BvbnNlEmAKD0dldEFJVGFza0NvbmZpZxIlLmJsb2NrbmluamEudjEuR2V0QUlUYXNrQ29uZmlnUmVxdWVzdBomLmJsb2NrbmluamEudjEuR2V0QUlUYXNrQ29uZmlnUmVzcG9uc2USZgoRTGlzdEFJVGFza0NvbmZpZ3MSJy5ibG9ja25pbmphLnYxLkxpc3RBSVRhc2tDb25maWdzUmVxdWVzdBooLmJsb2NrbmluamEudjEuTGlzdEFJVGFza0NvbmZpZ3NSZXNwb25zZRJgCg9TZXRBSVRhc2tDb25maWcSJS5ibG9ja25pbmphLnYxLlNldEFJVGFza0NvbmZpZ1JlcXVlc3QaJi5ibG9ja25pbmphLnYxLlNldEFJVGFza0NvbmZpZ1Jlc3BvbnNlEmkKEkRlbGV0ZUFJVGFza0NvbmZpZxIoLmJsb2NrbmluamEudjEuRGVsZXRlQUlUYXNrQ29uZmlnUmVxdWVzdBopLmJsb2NrbmluamEudjEuRGVsZXRlQUlUYXNrQ29uZmlnUmVzcG9uc2USYAoPR2VuZXJhdGVTRU9NZXRhEiUuYmxvY2tuaW5qYS52MS5HZW5lcmF0ZVNFT01ldGFSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5HZW5lcmF0ZVNFT01ldGFSZXNwb25zZRJ4ChdMaXN0UmVnaXN0ZXJlZEFJQWN0aW9ucxItLmJsb2NrbmluamEudjEuTGlzdFJlZ2lzdGVyZWRBSUFjdGlvbnNSZXF1ZXN0Gi4uYmxvY2tuaW5qYS52MS5MaXN0UmVnaXN0ZXJlZEFJQWN0aW9uc1Jlc3BvbnNlQr0BChFjb20uYmxvY2tuaW5qYS52MUIHQWlQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_google_protobuf_timestamp], +); +/** + * AI provider types + * + * @generated from enum blockninja.v1.AIProvider + */ +var AIProvider; +(function (AIProvider) { + /** + * @generated from enum value: AI_PROVIDER_UNSPECIFIED = 0; + */ + AIProvider[(AIProvider["AI_PROVIDER_UNSPECIFIED"] = 0)] = "AI_PROVIDER_UNSPECIFIED"; + /** + * @generated from enum value: AI_PROVIDER_OPENAI = 1; + */ + AIProvider[(AIProvider["AI_PROVIDER_OPENAI"] = 1)] = "AI_PROVIDER_OPENAI"; + /** + * @generated from enum value: AI_PROVIDER_ANTHROPIC = 2; + */ + AIProvider[(AIProvider["AI_PROVIDER_ANTHROPIC"] = 2)] = "AI_PROVIDER_ANTHROPIC"; +})(AIProvider || (AIProvider = {})); +/** + * AI Task types for the action matrix + * + * @generated from enum blockninja.v1.AITask + */ +var AITask; +(function (AITask) { + /** + * @generated from enum value: AI_TASK_UNSPECIFIED = 0; + */ + AITask[(AITask["AI_TASK_UNSPECIFIED"] = 0)] = "AI_TASK_UNSPECIFIED"; + /** + * Generate alt text for images + * + * @generated from enum value: AI_TASK_IMAGE_ALT_TEXT = 1; + */ + AITask[(AITask["AI_TASK_IMAGE_ALT_TEXT"] = 1)] = "AI_TASK_IMAGE_ALT_TEXT"; + /** + * Generate tags for images + * + * @generated from enum value: AI_TASK_IMAGE_TAGS = 2; + */ + AITask[(AITask["AI_TASK_IMAGE_TAGS"] = 2)] = "AI_TASK_IMAGE_TAGS"; + /** + * Generate content ideas + * + * @generated from enum value: AI_TASK_CONTENT_SUGGESTIONS = 3; + */ + AITask[(AITask["AI_TASK_CONTENT_SUGGESTIONS"] = 3)] = "AI_TASK_CONTENT_SUGGESTIONS"; + /** + * Generate meta descriptions + * + * @generated from enum value: AI_TASK_META_DESCRIPTION = 4; + */ + AITask[(AITask["AI_TASK_META_DESCRIPTION"] = 4)] = "AI_TASK_META_DESCRIPTION"; + /** + * Generate smart blocks from prompts + * + * @generated from enum value: AI_TASK_SMART_BLOCK = 5; + */ + AITask[(AITask["AI_TASK_SMART_BLOCK"] = 5)] = "AI_TASK_SMART_BLOCK"; + /** + * Assist/modify existing smart blocks + * + * @generated from enum value: AI_TASK_SMART_BLOCK_ASSIST = 6; + */ + AITask[(AITask["AI_TASK_SMART_BLOCK_ASSIST"] = 6)] = "AI_TASK_SMART_BLOCK_ASSIST"; + /** + * Generate maintenance/coming soon messages + * + * @generated from enum value: AI_TASK_SITE_STATUS_MESSAGE = 7; + */ + AITask[(AITask["AI_TASK_SITE_STATUS_MESSAGE"] = 7)] = "AI_TASK_SITE_STATUS_MESSAGE"; + /** + * Generate block title from content + * + * @generated from enum value: AI_TASK_BLOCK_TITLE = 8; + */ + AITask[(AITask["AI_TASK_BLOCK_TITLE"] = 8)] = "AI_TASK_BLOCK_TITLE"; + /** + * Generate SEO meta (title, description, OG tags) + * + * @generated from enum value: AI_TASK_SEO_META = 9; + */ + AITask[(AITask["AI_TASK_SEO_META"] = 9)] = "AI_TASK_SEO_META"; +})(AITask || (AITask = {})); +/** + * SEO field types for generation + * + * @generated from enum blockninja.v1.SEOFieldType + */ +var SEOFieldType; +(function (SEOFieldType) { + /** + * @generated from enum value: SEO_FIELD_TYPE_UNSPECIFIED = 0; + */ + SEOFieldType[(SEOFieldType["SEO_FIELD_TYPE_UNSPECIFIED"] = 0)] = "SEO_FIELD_TYPE_UNSPECIFIED"; + /** + * @generated from enum value: SEO_FIELD_TYPE_META_TITLE = 1; + */ + SEOFieldType[(SEOFieldType["SEO_FIELD_TYPE_META_TITLE"] = 1)] = "SEO_FIELD_TYPE_META_TITLE"; + /** + * @generated from enum value: SEO_FIELD_TYPE_META_DESCRIPTION = 2; + */ + SEOFieldType[(SEOFieldType["SEO_FIELD_TYPE_META_DESCRIPTION"] = 2)] = "SEO_FIELD_TYPE_META_DESCRIPTION"; + /** + * @generated from enum value: SEO_FIELD_TYPE_OG_TITLE = 3; + */ + SEOFieldType[(SEOFieldType["SEO_FIELD_TYPE_OG_TITLE"] = 3)] = "SEO_FIELD_TYPE_OG_TITLE"; + /** + * @generated from enum value: SEO_FIELD_TYPE_OG_DESCRIPTION = 4; + */ + SEOFieldType[(SEOFieldType["SEO_FIELD_TYPE_OG_DESCRIPTION"] = 4)] = "SEO_FIELD_TYPE_OG_DESCRIPTION"; +})(SEOFieldType || (SEOFieldType = {})); +/** + * AIService manages AI provider API keys and configuration + * + * @generated from service blockninja.v1.AIService + */ +const AIService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_ai, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/ai.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * List all AI provider keys (keys are masked) + * + * @generated from rpc blockninja.v1.AIService.ListAIKeys + */ +AIService.method.listAIKeys; +/** + * Add or update an AI provider key + * + * @generated from rpc blockninja.v1.AIService.SetAIKey + */ +AIService.method.setAIKey; +/** + * Delete an AI provider key + * + * @generated from rpc blockninja.v1.AIService.DeleteAIKey + */ +AIService.method.deleteAIKey; +/** + * Test an AI provider key for validity + * + * @generated from rpc blockninja.v1.AIService.TestAIKey + */ +AIService.method.testAIKey; +/** + * List available models for a provider (auto-refreshes if cache is stale) + * + * @generated from rpc blockninja.v1.AIService.ListAIModels + */ +AIService.method.listAIModels; +/** + * Force refresh models cache from provider API + * + * @generated from rpc blockninja.v1.AIService.RefreshAIModels + */ +AIService.method.refreshAIModels; +/** + * Get AI settings (active provider, model, etc.) + * + * @generated from rpc blockninja.v1.AIService.GetAISettings + */ +AIService.method.getAISettings; +/** + * Update AI settings + * + * @generated from rpc blockninja.v1.AIService.UpdateAISettings + */ +AIService.method.updateAISettings; +/** + * Get task-specific AI configuration + * + * @generated from rpc blockninja.v1.AIService.GetAITaskConfig + */ +AIService.method.getAITaskConfig; +/** + * List all task configurations + * + * @generated from rpc blockninja.v1.AIService.ListAITaskConfigs + */ +AIService.method.listAITaskConfigs; +/** + * Set task-specific AI configuration + * + * @generated from rpc blockninja.v1.AIService.SetAITaskConfig + */ +AIService.method.setAITaskConfig; +/** + * Delete task-specific configuration (falls back to default) + * + * @generated from rpc blockninja.v1.AIService.DeleteAITaskConfig + */ +AIService.method.deleteAITaskConfig; +/** + * Generate SEO meta data using AI + * + * @generated from rpc blockninja.v1.AIService.GenerateSEOMeta + */ +AIService.method.generateSEOMeta; +/** + * List all AI actions registered by plugins (static metadata from plugin registry) + * + * @generated from rpc blockninja.v1.AIService.ListRegisteredAIActions + */ +AIService.method.listRegisteredAIActions; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/ai_agents.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/ai_agents.proto. + */ +const file_blockninja_v1_ai_agents = /*@__PURE__*/ fileDesc( + "Ch1ibG9ja25pbmphL3YxL2FpX2FnZW50cy5wcm90bxINYmxvY2tuaW5qYS52MSKBAwoHQUlBZ2VudBIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEgwKBHNsdWcYAyABKAkSEwoLZGVzY3JpcHRpb24YBCABKAkSFQoNc3lzdGVtX3Byb21wdBgFIAEoCRITCgthaV9wcm92aWRlchgGIAEoCRINCgVtb2RlbBgHIAEoCRITCgt0ZW1wZXJhdHVyZRgIIAEoAhISCgptYXhfdG9rZW5zGAkgASgFEhIKCnRvb2xfc2x1Z3MYCiADKAkSGgoScmFnX2NvbGxlY3Rpb25faWRzGAsgAygJEhYKDm1pbl90aWVyX2xldmVsGAwgASgFEhQKDGlzX3B1Ymxpc2hlZBgNIAEoCBIXCg9hdmF0YXJfbWVkaWFfaWQYDiABKAkSLgoKY3JlYXRlZF9hdBgPIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKdXBkYXRlZF9hdBgQIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiEwoRTGlzdEFnZW50c1JlcXVlc3QiPAoSTGlzdEFnZW50c1Jlc3BvbnNlEiYKBmFnZW50cxgBIAMoCzIWLmJsb2NrbmluamEudjEuQUlBZ2VudCIdCg9HZXRBZ2VudFJlcXVlc3QSCgoCaWQYASABKAkiOQoQR2V0QWdlbnRSZXNwb25zZRIlCgVhZ2VudBgBIAEoCzIWLmJsb2NrbmluamEudjEuQUlBZ2VudCKgAgoSQ3JlYXRlQWdlbnRSZXF1ZXN0EgwKBG5hbWUYASABKAkSDAoEc2x1ZxgCIAEoCRITCgtkZXNjcmlwdGlvbhgDIAEoCRIVCg1zeXN0ZW1fcHJvbXB0GAQgASgJEhMKC2FpX3Byb3ZpZGVyGAUgASgJEg0KBW1vZGVsGAYgASgJEhMKC3RlbXBlcmF0dXJlGAcgASgCEhIKCm1heF90b2tlbnMYCCABKAUSEgoKdG9vbF9zbHVncxgJIAMoCRIaChJyYWdfY29sbGVjdGlvbl9pZHMYCiADKAkSFgoObWluX3RpZXJfbGV2ZWwYCyABKAUSFAoMaXNfcHVibGlzaGVkGAwgASgIEhcKD2F2YXRhcl9tZWRpYV9pZBgNIAEoCSI8ChNDcmVhdGVBZ2VudFJlc3BvbnNlEiUKBWFnZW50GAEgASgLMhYuYmxvY2tuaW5qYS52MS5BSUFnZW50IqwCChJVcGRhdGVBZ2VudFJlcXVlc3QSCgoCaWQYASABKAkSDAoEbmFtZRgCIAEoCRIMCgRzbHVnGAMgASgJEhMKC2Rlc2NyaXB0aW9uGAQgASgJEhUKDXN5c3RlbV9wcm9tcHQYBSABKAkSEwoLYWlfcHJvdmlkZXIYBiABKAkSDQoFbW9kZWwYByABKAkSEwoLdGVtcGVyYXR1cmUYCCABKAISEgoKbWF4X3Rva2VucxgJIAEoBRISCgp0b29sX3NsdWdzGAogAygJEhoKEnJhZ19jb2xsZWN0aW9uX2lkcxgLIAMoCRIWCg5taW5fdGllcl9sZXZlbBgMIAEoBRIUCgxpc19wdWJsaXNoZWQYDSABKAgSFwoPYXZhdGFyX21lZGlhX2lkGA4gASgJIjwKE1VwZGF0ZUFnZW50UmVzcG9uc2USJQoFYWdlbnQYASABKAsyFi5ibG9ja25pbmphLnYxLkFJQWdlbnQiIAoSRGVsZXRlQWdlbnRSZXF1ZXN0EgoKAmlkGAEgASgJIhUKE0RlbGV0ZUFnZW50UmVzcG9uc2Ui2QEKC0FJQWdlbnRUb29sEgoKAmlkGAEgASgJEgwKBHNsdWcYAiABKAkSDAoEbmFtZRgDIAEoCRITCgtkZXNjcmlwdGlvbhgEIAEoCRIUCgxoYW5kbGVyX3R5cGUYBSABKAkSFgoOaGFuZGxlcl9jb25maWcYBiABKAkSGAoQcGFyYW1ldGVyX3NjaGVtYRgHIAEoCRIVCg1zb3VyY2VfcGx1Z2luGAggASgJEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIhQKEkxpc3RBSVRvb2xzUmVxdWVzdCJAChNMaXN0QUlUb29sc1Jlc3BvbnNlEikKBXRvb2xzGAEgAygLMhouYmxvY2tuaW5qYS52MS5BSUFnZW50VG9vbCIeChBHZXRBSVRvb2xSZXF1ZXN0EgoKAmlkGAEgASgJIj0KEUdldEFJVG9vbFJlc3BvbnNlEigKBHRvb2wYASABKAsyGi5ibG9ja25pbmphLnYxLkFJQWdlbnRUb29sIo4BChNDcmVhdGVBSVRvb2xSZXF1ZXN0EgwKBHNsdWcYASABKAkSDAoEbmFtZRgCIAEoCRITCgtkZXNjcmlwdGlvbhgDIAEoCRIUCgxoYW5kbGVyX3R5cGUYBCABKAkSFgoOaGFuZGxlcl9jb25maWcYBSABKAkSGAoQcGFyYW1ldGVyX3NjaGVtYRgGIAEoCSJAChRDcmVhdGVBSVRvb2xSZXNwb25zZRIoCgR0b29sGAEgASgLMhouYmxvY2tuaW5qYS52MS5BSUFnZW50VG9vbCKMAQoTVXBkYXRlQUlUb29sUmVxdWVzdBIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEhQKDGhhbmRsZXJfdHlwZRgEIAEoCRIWCg5oYW5kbGVyX2NvbmZpZxgFIAEoCRIYChBwYXJhbWV0ZXJfc2NoZW1hGAYgASgJIkAKFFVwZGF0ZUFJVG9vbFJlc3BvbnNlEigKBHRvb2wYASABKAsyGi5ibG9ja25pbmphLnYxLkFJQWdlbnRUb29sIiEKE0RlbGV0ZUFJVG9vbFJlcXVlc3QSCgoCaWQYASABKAkiFgoURGVsZXRlQUlUb29sUmVzcG9uc2UigAIKDVJBR0NvbGxlY3Rpb24SCgoCaWQYASABKAkSDAoEbmFtZRgCIAEoCRITCgtzb3VyY2VfdHlwZRgDIAEoCRIVCg1zb3VyY2VfY29uZmlnGAQgASgJEhcKD2VtYmVkZGluZ19tb2RlbBgFIAEoCRIWCg5jaHVua19zdHJhdGVneRgGIAEoCRITCgtjaHVua19jb3VudBgHIAEoBRIzCg9sYXN0X2luZGV4ZWRfYXQYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIhsKGUxpc3RSQUdDb2xsZWN0aW9uc1JlcXVlc3QiTwoaTGlzdFJBR0NvbGxlY3Rpb25zUmVzcG9uc2USMQoLY29sbGVjdGlvbnMYASADKAsyHC5ibG9ja25pbmphLnYxLlJBR0NvbGxlY3Rpb24iJQoXR2V0UkFHQ29sbGVjdGlvblJlcXVlc3QSCgoCaWQYASABKAkiTAoYR2V0UkFHQ29sbGVjdGlvblJlc3BvbnNlEjAKCmNvbGxlY3Rpb24YASABKAsyHC5ibG9ja25pbmphLnYxLlJBR0NvbGxlY3Rpb24ihwEKGkNyZWF0ZVJBR0NvbGxlY3Rpb25SZXF1ZXN0EgwKBG5hbWUYASABKAkSEwoLc291cmNlX3R5cGUYAiABKAkSFQoNc291cmNlX2NvbmZpZxgDIAEoCRIXCg9lbWJlZGRpbmdfbW9kZWwYBCABKAkSFgoOY2h1bmtfc3RyYXRlZ3kYBSABKAkiTwobQ3JlYXRlUkFHQ29sbGVjdGlvblJlc3BvbnNlEjAKCmNvbGxlY3Rpb24YASABKAsyHC5ibG9ja25pbmphLnYxLlJBR0NvbGxlY3Rpb24ikwEKGlVwZGF0ZVJBR0NvbGxlY3Rpb25SZXF1ZXN0EgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSEwoLc291cmNlX3R5cGUYAyABKAkSFQoNc291cmNlX2NvbmZpZxgEIAEoCRIXCg9lbWJlZGRpbmdfbW9kZWwYBSABKAkSFgoOY2h1bmtfc3RyYXRlZ3kYBiABKAkiTwobVXBkYXRlUkFHQ29sbGVjdGlvblJlc3BvbnNlEjAKCmNvbGxlY3Rpb24YASABKAsyHC5ibG9ja25pbmphLnYxLlJBR0NvbGxlY3Rpb24iKAoaRGVsZXRlUkFHQ29sbGVjdGlvblJlcXVlc3QSCgoCaWQYASABKAkiHQobRGVsZXRlUkFHQ29sbGVjdGlvblJlc3BvbnNlIicKGUluZGV4UkFHQ29sbGVjdGlvblJlcXVlc3QSCgoCaWQYASABKAkiNAoaSW5kZXhSQUdDb2xsZWN0aW9uUmVzcG9uc2USFgoOY2h1bmtzX2NyZWF0ZWQYASABKAUixQEKDkFJQ29udmVyc2F0aW9uEgoKAmlkGAEgASgJEhAKCGFnZW50X2lkGAIgASgJEhIKCmFnZW50X25hbWUYAyABKAkSEgoKYWdlbnRfc2x1ZxgEIAEoCRINCgV0aXRsZRgFIAEoCRIuCgpjcmVhdGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKlAQoJQUlNZXNzYWdlEgoKAmlkGAEgASgJEgwKBHJvbGUYAiABKAkSDwoHY29udGVudBgDIAEoCRISCgp0b29sX2NhbGxzGAQgASgJEhQKDHRvb2xfcmVzdWx0cxgFIAEoCRITCgt0b2tlbnNfdXNlZBgGIAEoBRIuCgpjcmVhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCIZChdMaXN0UHVibGljQWdlbnRzUmVxdWVzdCJCChhMaXN0UHVibGljQWdlbnRzUmVzcG9uc2USJgoGYWdlbnRzGAEgAygLMhYuYmxvY2tuaW5qYS52MS5BSUFnZW50IjsKGExpc3RDb252ZXJzYXRpb25zUmVxdWVzdBIRCglwYWdlX3NpemUYASABKAUSDAoEcGFnZRgCIAEoBSJmChlMaXN0Q29udmVyc2F0aW9uc1Jlc3BvbnNlEjQKDWNvbnZlcnNhdGlvbnMYASADKAsyHS5ibG9ja25pbmphLnYxLkFJQ29udmVyc2F0aW9uEhMKC3RvdGFsX2NvdW50GAIgASgFIiQKFkdldENvbnZlcnNhdGlvblJlcXVlc3QSCgoCaWQYASABKAkiegoXR2V0Q29udmVyc2F0aW9uUmVzcG9uc2USMwoMY29udmVyc2F0aW9uGAEgASgLMh0uYmxvY2tuaW5qYS52MS5BSUNvbnZlcnNhdGlvbhIqCghtZXNzYWdlcxgCIAMoCzIYLmJsb2NrbmluamEudjEuQUlNZXNzYWdlIi0KGUNyZWF0ZUNvbnZlcnNhdGlvblJlcXVlc3QSEAoIYWdlbnRfaWQYASABKAkiUQoaQ3JlYXRlQ29udmVyc2F0aW9uUmVzcG9uc2USMwoMY29udmVyc2F0aW9uGAEgASgLMh0uYmxvY2tuaW5qYS52MS5BSUNvbnZlcnNhdGlvbiInChlEZWxldGVDb252ZXJzYXRpb25SZXF1ZXN0EgoKAmlkGAEgASgJIhwKGkRlbGV0ZUNvbnZlcnNhdGlvblJlc3BvbnNlIkMKF1NlbmRBZ2VudE1lc3NhZ2VSZXF1ZXN0EhcKD2NvbnZlcnNhdGlvbl9pZBgBIAEoCRIPCgdjb250ZW50GAIgASgJIn8KGFNlbmRBZ2VudE1lc3NhZ2VSZXNwb25zZRIuCgx1c2VyX21lc3NhZ2UYASABKAsyGC5ibG9ja25pbmphLnYxLkFJTWVzc2FnZRIzChFhc3Npc3RhbnRfbWVzc2FnZRgCIAEoCzIYLmJsb2NrbmluamEudjEuQUlNZXNzYWdlIhYKFEdldEFnZW50U3RhdHNSZXF1ZXN0IrABChVHZXRBZ2VudFN0YXRzUmVzcG9uc2USFAoMdG90YWxfYWdlbnRzGAEgASgFEhgKEHB1Ymxpc2hlZF9hZ2VudHMYAiABKAUSGwoTdG90YWxfY29udmVyc2F0aW9ucxgDIAEoBRIWCg50b3RhbF9tZXNzYWdlcxgEIAEoBRITCgt0b3RhbF90b29scxgFIAEoBRIdChV0b3RhbF9yYWdfY29sbGVjdGlvbnMYBiABKAUynxEKDkFJQWdlbnRTZXJ2aWNlElEKCkxpc3RBZ2VudHMSIC5ibG9ja25pbmphLnYxLkxpc3RBZ2VudHNSZXF1ZXN0GiEuYmxvY2tuaW5qYS52MS5MaXN0QWdlbnRzUmVzcG9uc2USSwoIR2V0QWdlbnQSHi5ibG9ja25pbmphLnYxLkdldEFnZW50UmVxdWVzdBofLmJsb2NrbmluamEudjEuR2V0QWdlbnRSZXNwb25zZRJUCgtDcmVhdGVBZ2VudBIhLmJsb2NrbmluamEudjEuQ3JlYXRlQWdlbnRSZXF1ZXN0GiIuYmxvY2tuaW5qYS52MS5DcmVhdGVBZ2VudFJlc3BvbnNlElQKC1VwZGF0ZUFnZW50EiEuYmxvY2tuaW5qYS52MS5VcGRhdGVBZ2VudFJlcXVlc3QaIi5ibG9ja25pbmphLnYxLlVwZGF0ZUFnZW50UmVzcG9uc2USVAoLRGVsZXRlQWdlbnQSIS5ibG9ja25pbmphLnYxLkRlbGV0ZUFnZW50UmVxdWVzdBoiLmJsb2NrbmluamEudjEuRGVsZXRlQWdlbnRSZXNwb25zZRJSCglMaXN0VG9vbHMSIS5ibG9ja25pbmphLnYxLkxpc3RBSVRvb2xzUmVxdWVzdBoiLmJsb2NrbmluamEudjEuTGlzdEFJVG9vbHNSZXNwb25zZRJMCgdHZXRUb29sEh8uYmxvY2tuaW5qYS52MS5HZXRBSVRvb2xSZXF1ZXN0GiAuYmxvY2tuaW5qYS52MS5HZXRBSVRvb2xSZXNwb25zZRJVCgpDcmVhdGVUb29sEiIuYmxvY2tuaW5qYS52MS5DcmVhdGVBSVRvb2xSZXF1ZXN0GiMuYmxvY2tuaW5qYS52MS5DcmVhdGVBSVRvb2xSZXNwb25zZRJVCgpVcGRhdGVUb29sEiIuYmxvY2tuaW5qYS52MS5VcGRhdGVBSVRvb2xSZXF1ZXN0GiMuYmxvY2tuaW5qYS52MS5VcGRhdGVBSVRvb2xSZXNwb25zZRJVCgpEZWxldGVUb29sEiIuYmxvY2tuaW5qYS52MS5EZWxldGVBSVRvb2xSZXF1ZXN0GiMuYmxvY2tuaW5qYS52MS5EZWxldGVBSVRvb2xSZXNwb25zZRJpChJMaXN0UkFHQ29sbGVjdGlvbnMSKC5ibG9ja25pbmphLnYxLkxpc3RSQUdDb2xsZWN0aW9uc1JlcXVlc3QaKS5ibG9ja25pbmphLnYxLkxpc3RSQUdDb2xsZWN0aW9uc1Jlc3BvbnNlEmMKEEdldFJBR0NvbGxlY3Rpb24SJi5ibG9ja25pbmphLnYxLkdldFJBR0NvbGxlY3Rpb25SZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5HZXRSQUdDb2xsZWN0aW9uUmVzcG9uc2USbAoTQ3JlYXRlUkFHQ29sbGVjdGlvbhIpLmJsb2NrbmluamEudjEuQ3JlYXRlUkFHQ29sbGVjdGlvblJlcXVlc3QaKi5ibG9ja25pbmphLnYxLkNyZWF0ZVJBR0NvbGxlY3Rpb25SZXNwb25zZRJsChNVcGRhdGVSQUdDb2xsZWN0aW9uEikuYmxvY2tuaW5qYS52MS5VcGRhdGVSQUdDb2xsZWN0aW9uUmVxdWVzdBoqLmJsb2NrbmluamEudjEuVXBkYXRlUkFHQ29sbGVjdGlvblJlc3BvbnNlEmwKE0RlbGV0ZVJBR0NvbGxlY3Rpb24SKS5ibG9ja25pbmphLnYxLkRlbGV0ZVJBR0NvbGxlY3Rpb25SZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5EZWxldGVSQUdDb2xsZWN0aW9uUmVzcG9uc2USaQoSSW5kZXhSQUdDb2xsZWN0aW9uEiguYmxvY2tuaW5qYS52MS5JbmRleFJBR0NvbGxlY3Rpb25SZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5JbmRleFJBR0NvbGxlY3Rpb25SZXNwb25zZRJjChBMaXN0UHVibGljQWdlbnRzEiYuYmxvY2tuaW5qYS52MS5MaXN0UHVibGljQWdlbnRzUmVxdWVzdBonLmJsb2NrbmluamEudjEuTGlzdFB1YmxpY0FnZW50c1Jlc3BvbnNlEmYKEUxpc3RDb252ZXJzYXRpb25zEicuYmxvY2tuaW5qYS52MS5MaXN0Q29udmVyc2F0aW9uc1JlcXVlc3QaKC5ibG9ja25pbmphLnYxLkxpc3RDb252ZXJzYXRpb25zUmVzcG9uc2USYAoPR2V0Q29udmVyc2F0aW9uEiUuYmxvY2tuaW5qYS52MS5HZXRDb252ZXJzYXRpb25SZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5HZXRDb252ZXJzYXRpb25SZXNwb25zZRJpChJDcmVhdGVDb252ZXJzYXRpb24SKC5ibG9ja25pbmphLnYxLkNyZWF0ZUNvbnZlcnNhdGlvblJlcXVlc3QaKS5ibG9ja25pbmphLnYxLkNyZWF0ZUNvbnZlcnNhdGlvblJlc3BvbnNlEmkKEkRlbGV0ZUNvbnZlcnNhdGlvbhIoLmJsb2NrbmluamEudjEuRGVsZXRlQ29udmVyc2F0aW9uUmVxdWVzdBopLmJsb2NrbmluamEudjEuRGVsZXRlQ29udmVyc2F0aW9uUmVzcG9uc2USXgoLU2VuZE1lc3NhZ2USJi5ibG9ja25pbmphLnYxLlNlbmRBZ2VudE1lc3NhZ2VSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5TZW5kQWdlbnRNZXNzYWdlUmVzcG9uc2USWgoNR2V0QWdlbnRTdGF0cxIjLmJsb2NrbmluamEudjEuR2V0QWdlbnRTdGF0c1JlcXVlc3QaJC5ibG9ja25pbmphLnYxLkdldEFnZW50U3RhdHNSZXNwb25zZULDAQoRY29tLmJsb2NrbmluamEudjFCDUFpQWdlbnRzUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_google_protobuf_timestamp], +); +/** + * @generated from service blockninja.v1.AIAgentService + */ +const AIAgentService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_ai_agents, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/ai_agents.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * @generated from rpc blockninja.v1.AIAgentService.ListAgents + */ +AIAgentService.method.listAgents; +/** + * @generated from rpc blockninja.v1.AIAgentService.GetAgent + */ +AIAgentService.method.getAgent; +/** + * @generated from rpc blockninja.v1.AIAgentService.CreateAgent + */ +AIAgentService.method.createAgent; +/** + * @generated from rpc blockninja.v1.AIAgentService.UpdateAgent + */ +AIAgentService.method.updateAgent; +/** + * @generated from rpc blockninja.v1.AIAgentService.DeleteAgent + */ +AIAgentService.method.deleteAgent; +/** + * @generated from rpc blockninja.v1.AIAgentService.ListTools + */ +AIAgentService.method.listTools; +/** + * @generated from rpc blockninja.v1.AIAgentService.GetTool + */ +AIAgentService.method.getTool; +/** + * @generated from rpc blockninja.v1.AIAgentService.CreateTool + */ +AIAgentService.method.createTool; +/** + * @generated from rpc blockninja.v1.AIAgentService.UpdateTool + */ +AIAgentService.method.updateTool; +/** + * @generated from rpc blockninja.v1.AIAgentService.DeleteTool + */ +AIAgentService.method.deleteTool; +/** + * @generated from rpc blockninja.v1.AIAgentService.ListRAGCollections + */ +AIAgentService.method.listRAGCollections; +/** + * @generated from rpc blockninja.v1.AIAgentService.GetRAGCollection + */ +AIAgentService.method.getRAGCollection; +/** + * @generated from rpc blockninja.v1.AIAgentService.CreateRAGCollection + */ +AIAgentService.method.createRAGCollection; +/** + * @generated from rpc blockninja.v1.AIAgentService.UpdateRAGCollection + */ +AIAgentService.method.updateRAGCollection; +/** + * @generated from rpc blockninja.v1.AIAgentService.DeleteRAGCollection + */ +AIAgentService.method.deleteRAGCollection; +/** + * @generated from rpc blockninja.v1.AIAgentService.IndexRAGCollection + */ +AIAgentService.method.indexRAGCollection; +/** + * @generated from rpc blockninja.v1.AIAgentService.ListPublicAgents + */ +AIAgentService.method.listPublicAgents; +/** + * @generated from rpc blockninja.v1.AIAgentService.ListConversations + */ +AIAgentService.method.listConversations; +/** + * @generated from rpc blockninja.v1.AIAgentService.GetConversation + */ +AIAgentService.method.getConversation; +/** + * @generated from rpc blockninja.v1.AIAgentService.CreateConversation + */ +AIAgentService.method.createConversation; +/** + * @generated from rpc blockninja.v1.AIAgentService.DeleteConversation + */ +AIAgentService.method.deleteConversation; +/** + * @generated from rpc blockninja.v1.AIAgentService.SendMessage + */ +AIAgentService.method.sendMessage; +/** + * @generated from rpc blockninja.v1.AIAgentService.GetAgentStats + */ +AIAgentService.method.getAgentStats; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/pages.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/pages.proto. + */ +const file_blockninja_v1_pages = /*@__PURE__*/ fileDesc( + "ChlibG9ja25pbmphL3YxL3BhZ2VzLnByb3RvEg1ibG9ja25pbmphLnYxIt8LCgRQYWdlEgoKAmlkGAEgASgJEgwKBHNsdWcYAiABKAkSFAoMdGVtcGxhdGVfa2V5GAMgASgJEg0KBXRpdGxlGAQgASgJEikKCGRvY3VtZW50GAUgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIpCgZzdGF0dXMYBiABKA4yGS5ibG9ja25pbmphLnYxLlBhZ2VTdGF0dXMSDwoHdmVyc2lvbhgHIAEoBRI1CgxwdWJsaXNoZWRfYXQYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSACIAQESLgoKY3JlYXRlZF9hdBgJIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKdXBkYXRlZF9hdBgKIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFwoKY3JlYXRlZF9ieRgLIAEoCUgBiAEBEhcKCnVwZGF0ZWRfYnkYDCABKAlIAogBARIWCglwYXJlbnRfaWQYDSABKAlIA4gBARIqCglwb3N0X3R5cGUYDiABKA4yFy5ibG9ja25pbmphLnYxLlBvc3RUeXBlEhQKB2V4Y2VycHQYDyABKAlIBIgBARIeChFmZWF0dXJlZF9pbWFnZV9pZBgQIAEoCUgFiAEBEhYKCWF1dGhvcl9pZBgRIAEoCUgGiAEBEiEKFHJlYWRpbmdfdGltZV9taW51dGVzGBIgASgFSAeIAQESGAoLaXNfZmVhdHVyZWQYEyABKAhICIgBARI3ChBzeXN0ZW1fcGFnZV90eXBlGBQgASgOMh0uYmxvY2tuaW5qYS52MS5TeXN0ZW1QYWdlVHlwZRIkChdyZWRpcmVjdF90YXJnZXRfcGFnZV9pZBgVIAEoCUgJiAEBEiAKE3JlZGlyZWN0X3RhcmdldF91cmwYFiABKAlICogBARIVCg1yZWRpcmVjdF90eXBlGBcgASgFEh4KEXB1Ymxpc2hlZF92ZXJzaW9uGBggASgFSAuIAQESHwoXaGFzX3VucHVibGlzaGVkX2NoYW5nZXMYGSABKAgSNQoMc2NoZWR1bGVkX2F0GBogASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgMiAEBEhsKDm1hc3Rlcl9wYWdlX2lkGBsgASgJSA2IAQESHAoPbWFzdGVyX3BhZ2Vfa2V5GBwgASgJSA6IAQESMgoJcG9zdF9kYXRlGB0gASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgPiAEBEhkKDGRlcml2ZWRfc2x1ZxgeIAEoCUgQiAEBEhcKCm1vdW50X3BhdGgYHyABKAlIEYgBARIfChJkZXRhaWxfc291cmNlX3R5cGUYICABKAlIEogBARIdChBkZXRhaWxfc291cmNlX2lkGCEgASgJSBOIAQESHgoRZGV0YWlsX3NsdWdfZmllbGQYIiABKAlIFIgBAUIPCg1fcHVibGlzaGVkX2F0Qg0KC19jcmVhdGVkX2J5Qg0KC191cGRhdGVkX2J5QgwKCl9wYXJlbnRfaWRCCgoIX2V4Y2VycHRCFAoSX2ZlYXR1cmVkX2ltYWdlX2lkQgwKCl9hdXRob3JfaWRCFwoVX3JlYWRpbmdfdGltZV9taW51dGVzQg4KDF9pc19mZWF0dXJlZEIaChhfcmVkaXJlY3RfdGFyZ2V0X3BhZ2VfaWRCFgoUX3JlZGlyZWN0X3RhcmdldF91cmxCFAoSX3B1Ymxpc2hlZF92ZXJzaW9uQg8KDV9zY2hlZHVsZWRfYXRCEQoPX21hc3Rlcl9wYWdlX2lkQhIKEF9tYXN0ZXJfcGFnZV9rZXlCDAoKX3Bvc3RfZGF0ZUIPCg1fZGVyaXZlZF9zbHVnQg0KC19tb3VudF9wYXRoQhUKE19kZXRhaWxfc291cmNlX3R5cGVCEwoRX2RldGFpbF9zb3VyY2VfaWRCFAoSX2RldGFpbF9zbHVnX2ZpZWxkIoUCCgtQYWdlVmVyc2lvbhIKCgJpZBgBIAEoCRIPCgdwYWdlX2lkGAIgASgJEg8KB3ZlcnNpb24YAyABKAUSFAoMdGVtcGxhdGVfa2V5GAQgASgJEg0KBXRpdGxlGAUgASgJEikKCGRvY3VtZW50GAYgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIwCgxwdWJsaXNoZWRfYXQYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhkKDHB1Ymxpc2hlZF9ieRgIIAEoCUgAiAEBEhEKBG5hbWUYCSABKAlIAYgBAUIPCg1fcHVibGlzaGVkX2J5QgcKBV9uYW1lIioKCFRlbXBsYXRlEgsKA2tleRgBIAEoCRIRCglpc19sb2FkZWQYAiABKAgi6QEKEExpc3RQYWdlc1JlcXVlc3QSLQoKcGFnaW5hdGlvbhgBIAEoCzIZLmJsb2NrbmluamEudjEuUGFnaW5hdGlvbhI1Cg1zdGF0dXNfZmlsdGVyGAIgASgOMhkuYmxvY2tuaW5qYS52MS5QYWdlU3RhdHVzSACIAQESEwoGc2VhcmNoGAMgASgJSAGIAQESLwoJcG9zdF90eXBlGAQgASgOMhcuYmxvY2tuaW5qYS52MS5Qb3N0VHlwZUgCiAEBQhAKDl9zdGF0dXNfZmlsdGVyQgkKB19zZWFyY2hCDAoKX3Bvc3RfdHlwZSJuChFMaXN0UGFnZXNSZXNwb25zZRIiCgVwYWdlcxgBIAMoCzITLmJsb2NrbmluamEudjEuUGFnZRI1CgpwYWdpbmF0aW9uGAIgASgLMiEuYmxvY2tuaW5qYS52MS5QYWdpbmF0aW9uUmVzcG9uc2UiHAoOR2V0UGFnZVJlcXVlc3QSCgoCaWQYASABKAkiNAoPR2V0UGFnZVJlc3BvbnNlEiEKBHBhZ2UYASABKAsyEy5ibG9ja25pbmphLnYxLlBhZ2UitAYKEUNyZWF0ZVBhZ2VSZXF1ZXN0EgwKBHNsdWcYASABKAkSFAoMdGVtcGxhdGVfa2V5GAIgASgJEg0KBXRpdGxlGAMgASgJEikKCGRvY3VtZW50GAQgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIWCglwYXJlbnRfaWQYBSABKAlIAIgBARIvCglwb3N0X3R5cGUYBiABKA4yFy5ibG9ja25pbmphLnYxLlBvc3RUeXBlSAGIAQESFAoHZXhjZXJwdBgHIAEoCUgCiAEBEh4KEWZlYXR1cmVkX2ltYWdlX2lkGAggASgJSAOIAQESFgoJYXV0aG9yX2lkGAkgASgJSASIAQESGAoLaXNfZmVhdHVyZWQYCiABKAhIBYgBARIkChdyZWRpcmVjdF90YXJnZXRfcGFnZV9pZBgLIAEoCUgGiAEBEiAKE3JlZGlyZWN0X3RhcmdldF91cmwYDCABKAlIB4gBARIaCg1yZWRpcmVjdF90eXBlGA0gASgFSAiIAQESMgoJcG9zdF9kYXRlGA4gASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgJiAEBEhcKCm1vdW50X3BhdGgYDyABKAlICogBARIfChJkZXRhaWxfc291cmNlX3R5cGUYECABKAlIC4gBARIdChBkZXRhaWxfc291cmNlX2lkGBEgASgJSAyIAQESHgoRZGV0YWlsX3NsdWdfZmllbGQYEiABKAlIDYgBAUIMCgpfcGFyZW50X2lkQgwKCl9wb3N0X3R5cGVCCgoIX2V4Y2VycHRCFAoSX2ZlYXR1cmVkX2ltYWdlX2lkQgwKCl9hdXRob3JfaWRCDgoMX2lzX2ZlYXR1cmVkQhoKGF9yZWRpcmVjdF90YXJnZXRfcGFnZV9pZEIWChRfcmVkaXJlY3RfdGFyZ2V0X3VybEIQCg5fcmVkaXJlY3RfdHlwZUIMCgpfcG9zdF9kYXRlQg0KC19tb3VudF9wYXRoQhUKE19kZXRhaWxfc291cmNlX3R5cGVCEwoRX2RldGFpbF9zb3VyY2VfaWRCFAoSX2RldGFpbF9zbHVnX2ZpZWxkIjcKEkNyZWF0ZVBhZ2VSZXNwb25zZRIhCgRwYWdlGAEgASgLMhMuYmxvY2tuaW5qYS52MS5QYWdlIoUHChFVcGRhdGVQYWdlUmVxdWVzdBIKCgJpZBgBIAEoCRIRCgRzbHVnGAIgASgJSACIAQESGQoMdGVtcGxhdGVfa2V5GAMgASgJSAGIAQESEgoFdGl0bGUYBCABKAlIAogBARIuCghkb2N1bWVudBgFIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3RIA4gBARIWCglwYXJlbnRfaWQYBiABKAlIBIgBARIvCglwb3N0X3R5cGUYByABKA4yFy5ibG9ja25pbmphLnYxLlBvc3RUeXBlSAWIAQESFAoHZXhjZXJwdBgIIAEoCUgGiAEBEh4KEWZlYXR1cmVkX2ltYWdlX2lkGAkgASgJSAeIAQESFgoJYXV0aG9yX2lkGAogASgJSAiIAQESGAoLaXNfZmVhdHVyZWQYCyABKAhICYgBARIkChdyZWRpcmVjdF90YXJnZXRfcGFnZV9pZBgMIAEoCUgKiAEBEiAKE3JlZGlyZWN0X3RhcmdldF91cmwYDSABKAlIC4gBARIaCg1yZWRpcmVjdF90eXBlGA4gASgFSAyIAQESMgoJcG9zdF9kYXRlGA8gASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgNiAEBEhcKCm1vdW50X3BhdGgYECABKAlIDogBARIfChJkZXRhaWxfc291cmNlX3R5cGUYESABKAlID4gBARIdChBkZXRhaWxfc291cmNlX2lkGBIgASgJSBCIAQESHgoRZGV0YWlsX3NsdWdfZmllbGQYEyABKAlIEYgBAUIHCgVfc2x1Z0IPCg1fdGVtcGxhdGVfa2V5QggKBl90aXRsZUILCglfZG9jdW1lbnRCDAoKX3BhcmVudF9pZEIMCgpfcG9zdF90eXBlQgoKCF9leGNlcnB0QhQKEl9mZWF0dXJlZF9pbWFnZV9pZEIMCgpfYXV0aG9yX2lkQg4KDF9pc19mZWF0dXJlZEIaChhfcmVkaXJlY3RfdGFyZ2V0X3BhZ2VfaWRCFgoUX3JlZGlyZWN0X3RhcmdldF91cmxCEAoOX3JlZGlyZWN0X3R5cGVCDAoKX3Bvc3RfZGF0ZUINCgtfbW91bnRfcGF0aEIVChNfZGV0YWlsX3NvdXJjZV90eXBlQhMKEV9kZXRhaWxfc291cmNlX2lkQhQKEl9kZXRhaWxfc2x1Z19maWVsZCI3ChJVcGRhdGVQYWdlUmVzcG9uc2USIQoEcGFnZRgBIAEoCzITLmJsb2NrbmluamEudjEuUGFnZSIfChFEZWxldGVQYWdlUmVxdWVzdBIKCgJpZBgBIAEoCSIUChJEZWxldGVQYWdlUmVzcG9uc2UiIAoSUHVibGlzaFBhZ2VSZXF1ZXN0EgoKAmlkGAEgASgJIjgKE1B1Ymxpc2hQYWdlUmVzcG9uc2USIQoEcGFnZRgBIAEoCzITLmJsb2NrbmluamEudjEuUGFnZSIiChRVbnB1Ymxpc2hQYWdlUmVxdWVzdBIKCgJpZBgBIAEoCSI6ChVVbnB1Ymxpc2hQYWdlUmVzcG9uc2USIQoEcGFnZRgBIAEoCzITLmJsb2NrbmluamEudjEuUGFnZSJZChdMaXN0UGFnZVZlcnNpb25zUmVxdWVzdBIPCgdwYWdlX2lkGAEgASgJEi0KCnBhZ2luYXRpb24YAiABKAsyGS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb24ifwoYTGlzdFBhZ2VWZXJzaW9uc1Jlc3BvbnNlEiwKCHZlcnNpb25zGAEgAygLMhouYmxvY2tuaW5qYS52MS5QYWdlVmVyc2lvbhI1CgpwYWdpbmF0aW9uGAIgASgLMiEuYmxvY2tuaW5qYS52MS5QYWdpbmF0aW9uUmVzcG9uc2UiNwoTUm9sbGJhY2tQYWdlUmVxdWVzdBIPCgdwYWdlX2lkGAEgASgJEg8KB3ZlcnNpb24YAiABKAUiOQoUUm9sbGJhY2tQYWdlUmVzcG9uc2USIQoEcGFnZRgBIAEoCzITLmJsb2NrbmluamEudjEuUGFnZSJOChxVcGRhdGVQYWdlVmVyc2lvbk5hbWVSZXF1ZXN0Eg8KB3BhZ2VfaWQYASABKAkSDwoHdmVyc2lvbhgCIAEoBRIMCgRuYW1lGAMgASgJIkwKHVVwZGF0ZVBhZ2VWZXJzaW9uTmFtZVJlc3BvbnNlEisKB3ZlcnNpb24YASABKAsyGi5ibG9ja25pbmphLnYxLlBhZ2VWZXJzaW9uIhYKFExpc3RUZW1wbGF0ZXNSZXF1ZXN0IkMKFUxpc3RUZW1wbGF0ZXNSZXNwb25zZRIqCgl0ZW1wbGF0ZXMYASADKAsyFy5ibG9ja25pbmphLnYxLlRlbXBsYXRlIhgKFkxpc3RTeXN0ZW1QYWdlc1JlcXVlc3QiPQoXTGlzdFN5c3RlbVBhZ2VzUmVzcG9uc2USIgoFcGFnZXMYASADKAsyEy5ibG9ja25pbmphLnYxLlBhZ2UiUQoWUmVzZXRTeXN0ZW1QYWdlUmVxdWVzdBI3ChBzeXN0ZW1fcGFnZV90eXBlGAEgASgOMh0uYmxvY2tuaW5qYS52MS5TeXN0ZW1QYWdlVHlwZSI8ChdSZXNldFN5c3RlbVBhZ2VSZXNwb25zZRIhCgRwYWdlGAEgASgLMhMuYmxvY2tuaW5qYS52MS5QYWdlIigKGkRpc2NhcmREcmFmdENoYW5nZXNSZXF1ZXN0EgoKAmlkGAEgASgJIkAKG0Rpc2NhcmREcmFmdENoYW5nZXNSZXNwb25zZRIhCgRwYWdlGAEgASgLMhMuYmxvY2tuaW5qYS52MS5QYWdlIlAKE0J1bGtPcGVyYXRpb25SZXN1bHQSCgoCaWQYASABKAkSDwoHc3VjY2VzcxgCIAEoCBISCgVlcnJvchgDIAEoCUgAiAEBQggKBl9lcnJvciJbChZCdWxrRGVsZXRlUGFnZXNSZXF1ZXN0EgsKA2lkcxgBIAMoCRI0Cg5jaGlsZF9oYW5kbGluZxgCIAEoDjIcLmJsb2NrbmluamEudjEuQ2hpbGRIYW5kbGluZyJ7ChdCdWxrRGVsZXRlUGFnZXNSZXNwb25zZRIzCgdyZXN1bHRzGAEgAygLMiIuYmxvY2tuaW5qYS52MS5CdWxrT3BlcmF0aW9uUmVzdWx0EhUKDWRlbGV0ZWRfY291bnQYAiABKAUSFAoMZmFpbGVkX2NvdW50GAMgASgFIlEKFEJ1bGtNb3ZlUGFnZXNSZXF1ZXN0EgsKA2lkcxgBIAMoCRIaCg1uZXdfcGFyZW50X2lkGAIgASgJSACIAQFCEAoOX25ld19wYXJlbnRfaWQidwoVQnVsa01vdmVQYWdlc1Jlc3BvbnNlEjMKB3Jlc3VsdHMYASADKAsyIi5ibG9ja25pbmphLnYxLkJ1bGtPcGVyYXRpb25SZXN1bHQSEwoLbW92ZWRfY291bnQYAiABKAUSFAoMZmFpbGVkX2NvdW50GAMgASgFIiYKF0J1bGtQdWJsaXNoUGFnZXNSZXF1ZXN0EgsKA2lkcxgBIAMoCSJ+ChhCdWxrUHVibGlzaFBhZ2VzUmVzcG9uc2USMwoHcmVzdWx0cxgBIAMoCzIiLmJsb2NrbmluamEudjEuQnVsa09wZXJhdGlvblJlc3VsdBIXCg9wdWJsaXNoZWRfY291bnQYAiABKAUSFAoMZmFpbGVkX2NvdW50GAMgASgFIsIBCgxQcmV2aWV3VG9rZW4SCgoCaWQYASABKAkSDwoHcGFnZV9pZBgCIAEoCRINCgV0b2tlbhgDIAEoCRIuCgpjcmVhdGVkX2F0GAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgpleHBpcmVzX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIXCgpjcmVhdGVkX2J5GAYgASgJSACIAQFCDQoLX2NyZWF0ZWRfYnkiYgobR2VuZXJhdGVQcmV2aWV3VG9rZW5SZXF1ZXN0Eg8KB3BhZ2VfaWQYASABKAkSHQoQZXhwaXJlc19pbl9ob3VycxgCIAEoBUgAiAEBQhMKEV9leHBpcmVzX2luX2hvdXJzImcKHEdlbmVyYXRlUHJldmlld1Rva2VuUmVzcG9uc2USMgoNcHJldmlld190b2tlbhgBIAEoCzIbLmJsb2NrbmluamEudjEuUHJldmlld1Rva2VuEhMKC3ByZXZpZXdfdXJsGAIgASgJIlMKE1NjaGVkdWxlUGFnZVJlcXVlc3QSCgoCaWQYASABKAkSMAoMc2NoZWR1bGVkX2F0GAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCI5ChRTY2hlZHVsZVBhZ2VSZXNwb25zZRIhCgRwYWdlGAEgASgLMhMuYmxvY2tuaW5qYS52MS5QYWdlIiMKFVVuc2NoZWR1bGVQYWdlUmVxdWVzdBIKCgJpZBgBIAEoCSI7ChZVbnNjaGVkdWxlUGFnZVJlc3BvbnNlEiEKBHBhZ2UYASABKAsyEy5ibG9ja25pbmphLnYxLlBhZ2UiSgoZTGlzdFNjaGVkdWxlZFBhZ2VzUmVxdWVzdBItCgpwYWdpbmF0aW9uGAEgASgLMhkuYmxvY2tuaW5qYS52MS5QYWdpbmF0aW9uIncKGkxpc3RTY2hlZHVsZWRQYWdlc1Jlc3BvbnNlEiIKBXBhZ2VzGAEgAygLMhMuYmxvY2tuaW5qYS52MS5QYWdlEjUKCnBhZ2luYXRpb24YAiABKAsyIS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb25SZXNwb25zZSKxAgoKTWFzdGVyUGFnZRIKCgJpZBgBIAEoCRIXCg9tYXN0ZXJfcGFnZV9rZXkYAiABKAkSFAoMdGVtcGxhdGVfa2V5GAMgASgJEg0KBXRpdGxlGAQgASgJEikKCGRvY3VtZW50GAUgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIuCgpjcmVhdGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIXCgpjcmVhdGVkX2J5GAggASgJSACIAQESFwoKdXBkYXRlZF9ieRgJIAEoCUgBiAEBQg0KC19jcmVhdGVkX2J5Qg0KC191cGRhdGVkX2J5IkoKFkxpc3RNYXN0ZXJQYWdlc1JlcXVlc3QSHAoPdGVtcGxhdGVfZmlsdGVyGAEgASgJSACIAQFCEgoQX3RlbXBsYXRlX2ZpbHRlciJKChdMaXN0TWFzdGVyUGFnZXNSZXNwb25zZRIvCgxtYXN0ZXJfcGFnZXMYASADKAsyGS5ibG9ja25pbmphLnYxLk1hc3RlclBhZ2UiggEKF0NyZWF0ZU1hc3RlclBhZ2VSZXF1ZXN0EhcKD21hc3Rlcl9wYWdlX2tleRgBIAEoCRIUCgx0ZW1wbGF0ZV9rZXkYAiABKAkSDQoFdGl0bGUYAyABKAkSKQoIZG9jdW1lbnQYBCABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0IkoKGENyZWF0ZU1hc3RlclBhZ2VSZXNwb25zZRIuCgttYXN0ZXJfcGFnZRgBIAEoCzIZLmJsb2NrbmluamEudjEuTWFzdGVyUGFnZSJfChpEdXBsaWNhdGVNYXN0ZXJQYWdlUmVxdWVzdBIRCglzb3VyY2VfaWQYASABKAkSGwoTbmV3X21hc3Rlcl9wYWdlX2tleRgCIAEoCRIRCgluZXdfdGl0bGUYAyABKAkiTQobRHVwbGljYXRlTWFzdGVyUGFnZVJlc3BvbnNlEi4KC21hc3Rlcl9wYWdlGAEgASgLMhkuYmxvY2tuaW5qYS52MS5NYXN0ZXJQYWdlIpsBChVUZW1wbGF0ZU1hc3RlckRlZmF1bHQSCgoCaWQYASABKAkSFwoPc3lzdGVtX3RlbXBsYXRlGAIgASgJEhUKDXBhZ2VfdGVtcGxhdGUYAyABKAkSFgoObWFzdGVyX3BhZ2VfaWQYBCABKAkSLgoLbWFzdGVyX3BhZ2UYBSABKAsyGS5ibG9ja25pbmphLnYxLk1hc3RlclBhZ2UiYQoXU2V0RGVmYXVsdE1hc3RlclJlcXVlc3QSFwoPc3lzdGVtX3RlbXBsYXRlGAEgASgJEhUKDXBhZ2VfdGVtcGxhdGUYAiABKAkSFgoObWFzdGVyX3BhZ2VfaWQYAyABKAkiUQoYU2V0RGVmYXVsdE1hc3RlclJlc3BvbnNlEjUKB2RlZmF1bHQYASABKAsyJC5ibG9ja25pbmphLnYxLlRlbXBsYXRlTWFzdGVyRGVmYXVsdCJJChdHZXREZWZhdWx0TWFzdGVyUmVxdWVzdBIXCg9zeXN0ZW1fdGVtcGxhdGUYASABKAkSFQoNcGFnZV90ZW1wbGF0ZRgCIAEoCSJfChhHZXREZWZhdWx0TWFzdGVyUmVzcG9uc2USMwoLbWFzdGVyX3BhZ2UYASABKAsyGS5ibG9ja25pbmphLnYxLk1hc3RlclBhZ2VIAIgBAUIOCgxfbWFzdGVyX3BhZ2UiVwoUU2V0UGFnZU1hc3RlclJlcXVlc3QSDwoHcGFnZV9pZBgBIAEoCRIbCg5tYXN0ZXJfcGFnZV9pZBgCIAEoCUgAiAEBQhEKD19tYXN0ZXJfcGFnZV9pZCI6ChVTZXRQYWdlTWFzdGVyUmVzcG9uc2USIQoEcGFnZRgBIAEoCzITLmJsb2NrbmluamEudjEuUGFnZSpbCgpQYWdlU3RhdHVzEhsKF1BBR0VfU1RBVFVTX1VOU1BFQ0lGSUVEEAASFQoRUEFHRV9TVEFUVVNfRFJBRlQQARIZChVQQUdFX1NUQVRVU19QVUJMSVNIRUQQAiqRAQoIUG9zdFR5cGUSGQoVUE9TVF9UWVBFX1VOU1BFQ0lGSUVEEAASEgoOUE9TVF9UWVBFX1BBR0UQARISCg5QT1NUX1RZUEVfUE9TVBACEhQKEFBPU1RfVFlQRV9TWVNURU0QAxIWChJQT1NUX1RZUEVfUkVESVJFQ1QQBBIUChBQT1NUX1RZUEVfTUFTVEVSEAUqvwIKDlN5c3RlbVBhZ2VUeXBlEiAKHFNZU1RFTV9QQUdFX1RZUEVfVU5TUEVDSUZJRUQQABIeChpTWVNURU1fUEFHRV9UWVBFX05PVF9GT1VORBABEiEKHVNZU1RFTV9QQUdFX1RZUEVfU0VSVkVSX0VSUk9SEAISIAocU1lTVEVNX1BBR0VfVFlQRV9NQUlOVEVOQU5DRRADEiAKHFNZU1RFTV9QQUdFX1RZUEVfQ09NSU5HX1NPT04QBBInCiNTWVNURU1fUEFHRV9UWVBFX0JMT0dfUE9TVF9URU1QTEFURRAFEhsKF1NZU1RFTV9QQUdFX1RZUEVfQVVUSE9SEAYSHwobU1lTVEVNX1BBR0VfVFlQRV9CTE9HX0lOREVYEAcSHQoZU1lTVEVNX1BBR0VfVFlQRV9DQVRFR09SWRAIKmkKDUNoaWxkSGFuZGxpbmcSHgoaQ0hJTERfSEFORExJTkdfVU5TUEVDSUZJRUQQABIaChZDSElMRF9IQU5ETElOR19DQVNDQURFEAESHAoYQ0hJTERfSEFORExJTkdfTU9WRV9ST09UEAIypRQKDFBhZ2VzU2VydmljZRJOCglMaXN0UGFnZXMSHy5ibG9ja25pbmphLnYxLkxpc3RQYWdlc1JlcXVlc3QaIC5ibG9ja25pbmphLnYxLkxpc3RQYWdlc1Jlc3BvbnNlEkgKB0dldFBhZ2USHS5ibG9ja25pbmphLnYxLkdldFBhZ2VSZXF1ZXN0Gh4uYmxvY2tuaW5qYS52MS5HZXRQYWdlUmVzcG9uc2USUQoKQ3JlYXRlUGFnZRIgLmJsb2NrbmluamEudjEuQ3JlYXRlUGFnZVJlcXVlc3QaIS5ibG9ja25pbmphLnYxLkNyZWF0ZVBhZ2VSZXNwb25zZRJRCgpVcGRhdGVQYWdlEiAuYmxvY2tuaW5qYS52MS5VcGRhdGVQYWdlUmVxdWVzdBohLmJsb2NrbmluamEudjEuVXBkYXRlUGFnZVJlc3BvbnNlElEKCkRlbGV0ZVBhZ2USIC5ibG9ja25pbmphLnYxLkRlbGV0ZVBhZ2VSZXF1ZXN0GiEuYmxvY2tuaW5qYS52MS5EZWxldGVQYWdlUmVzcG9uc2USVAoLUHVibGlzaFBhZ2USIS5ibG9ja25pbmphLnYxLlB1Ymxpc2hQYWdlUmVxdWVzdBoiLmJsb2NrbmluamEudjEuUHVibGlzaFBhZ2VSZXNwb25zZRJaCg1VbnB1Ymxpc2hQYWdlEiMuYmxvY2tuaW5qYS52MS5VbnB1Ymxpc2hQYWdlUmVxdWVzdBokLmJsb2NrbmluamEudjEuVW5wdWJsaXNoUGFnZVJlc3BvbnNlEmMKEExpc3RQYWdlVmVyc2lvbnMSJi5ibG9ja25pbmphLnYxLkxpc3RQYWdlVmVyc2lvbnNSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5MaXN0UGFnZVZlcnNpb25zUmVzcG9uc2USVwoMUm9sbGJhY2tQYWdlEiIuYmxvY2tuaW5qYS52MS5Sb2xsYmFja1BhZ2VSZXF1ZXN0GiMuYmxvY2tuaW5qYS52MS5Sb2xsYmFja1BhZ2VSZXNwb25zZRJyChVVcGRhdGVQYWdlVmVyc2lvbk5hbWUSKy5ibG9ja25pbmphLnYxLlVwZGF0ZVBhZ2VWZXJzaW9uTmFtZVJlcXVlc3QaLC5ibG9ja25pbmphLnYxLlVwZGF0ZVBhZ2VWZXJzaW9uTmFtZVJlc3BvbnNlEloKDUxpc3RUZW1wbGF0ZXMSIy5ibG9ja25pbmphLnYxLkxpc3RUZW1wbGF0ZXNSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5MaXN0VGVtcGxhdGVzUmVzcG9uc2USYAoPTGlzdFN5c3RlbVBhZ2VzEiUuYmxvY2tuaW5qYS52MS5MaXN0U3lzdGVtUGFnZXNSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5MaXN0U3lzdGVtUGFnZXNSZXNwb25zZRJgCg9SZXNldFN5c3RlbVBhZ2USJS5ibG9ja25pbmphLnYxLlJlc2V0U3lzdGVtUGFnZVJlcXVlc3QaJi5ibG9ja25pbmphLnYxLlJlc2V0U3lzdGVtUGFnZVJlc3BvbnNlEmwKE0Rpc2NhcmREcmFmdENoYW5nZXMSKS5ibG9ja25pbmphLnYxLkRpc2NhcmREcmFmdENoYW5nZXNSZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5EaXNjYXJkRHJhZnRDaGFuZ2VzUmVzcG9uc2USYAoPQnVsa0RlbGV0ZVBhZ2VzEiUuYmxvY2tuaW5qYS52MS5CdWxrRGVsZXRlUGFnZXNSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5CdWxrRGVsZXRlUGFnZXNSZXNwb25zZRJaCg1CdWxrTW92ZVBhZ2VzEiMuYmxvY2tuaW5qYS52MS5CdWxrTW92ZVBhZ2VzUmVxdWVzdBokLmJsb2NrbmluamEudjEuQnVsa01vdmVQYWdlc1Jlc3BvbnNlEmMKEEJ1bGtQdWJsaXNoUGFnZXMSJi5ibG9ja25pbmphLnYxLkJ1bGtQdWJsaXNoUGFnZXNSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5CdWxrUHVibGlzaFBhZ2VzUmVzcG9uc2USbwoUR2VuZXJhdGVQcmV2aWV3VG9rZW4SKi5ibG9ja25pbmphLnYxLkdlbmVyYXRlUHJldmlld1Rva2VuUmVxdWVzdBorLmJsb2NrbmluamEudjEuR2VuZXJhdGVQcmV2aWV3VG9rZW5SZXNwb25zZRJXCgxTY2hlZHVsZVBhZ2USIi5ibG9ja25pbmphLnYxLlNjaGVkdWxlUGFnZVJlcXVlc3QaIy5ibG9ja25pbmphLnYxLlNjaGVkdWxlUGFnZVJlc3BvbnNlEl0KDlVuc2NoZWR1bGVQYWdlEiQuYmxvY2tuaW5qYS52MS5VbnNjaGVkdWxlUGFnZVJlcXVlc3QaJS5ibG9ja25pbmphLnYxLlVuc2NoZWR1bGVQYWdlUmVzcG9uc2USaQoSTGlzdFNjaGVkdWxlZFBhZ2VzEiguYmxvY2tuaW5qYS52MS5MaXN0U2NoZWR1bGVkUGFnZXNSZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5MaXN0U2NoZWR1bGVkUGFnZXNSZXNwb25zZRJgCg9MaXN0TWFzdGVyUGFnZXMSJS5ibG9ja25pbmphLnYxLkxpc3RNYXN0ZXJQYWdlc1JlcXVlc3QaJi5ibG9ja25pbmphLnYxLkxpc3RNYXN0ZXJQYWdlc1Jlc3BvbnNlEmMKEENyZWF0ZU1hc3RlclBhZ2USJi5ibG9ja25pbmphLnYxLkNyZWF0ZU1hc3RlclBhZ2VSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5DcmVhdGVNYXN0ZXJQYWdlUmVzcG9uc2USbAoTRHVwbGljYXRlTWFzdGVyUGFnZRIpLmJsb2NrbmluamEudjEuRHVwbGljYXRlTWFzdGVyUGFnZVJlcXVlc3QaKi5ibG9ja25pbmphLnYxLkR1cGxpY2F0ZU1hc3RlclBhZ2VSZXNwb25zZRJjChBTZXREZWZhdWx0TWFzdGVyEiYuYmxvY2tuaW5qYS52MS5TZXREZWZhdWx0TWFzdGVyUmVxdWVzdBonLmJsb2NrbmluamEudjEuU2V0RGVmYXVsdE1hc3RlclJlc3BvbnNlEmMKEEdldERlZmF1bHRNYXN0ZXISJi5ibG9ja25pbmphLnYxLkdldERlZmF1bHRNYXN0ZXJSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5HZXREZWZhdWx0TWFzdGVyUmVzcG9uc2USWgoNU2V0UGFnZU1hc3RlchIjLmJsb2NrbmluamEudjEuU2V0UGFnZU1hc3RlclJlcXVlc3QaJC5ibG9ja25pbmphLnYxLlNldFBhZ2VNYXN0ZXJSZXNwb25zZULAAQoRY29tLmJsb2NrbmluamEudjFCClBhZ2VzUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_blockninja_v1_common, file_google_protobuf_timestamp, file_google_protobuf_struct], +); +/** + * @generated from enum blockninja.v1.PageStatus + */ +var PageStatus; +(function (PageStatus) { + /** + * @generated from enum value: PAGE_STATUS_UNSPECIFIED = 0; + */ + PageStatus[(PageStatus["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * @generated from enum value: PAGE_STATUS_DRAFT = 1; + */ + PageStatus[(PageStatus["DRAFT"] = 1)] = "DRAFT"; + /** + * @generated from enum value: PAGE_STATUS_PUBLISHED = 2; + */ + PageStatus[(PageStatus["PUBLISHED"] = 2)] = "PUBLISHED"; +})(PageStatus || (PageStatus = {})); +/** + * @generated from enum blockninja.v1.PostType + */ +var PostType; +(function (PostType) { + /** + * @generated from enum value: POST_TYPE_UNSPECIFIED = 0; + */ + PostType[(PostType["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * @generated from enum value: POST_TYPE_PAGE = 1; + */ + PostType[(PostType["PAGE"] = 1)] = "PAGE"; + /** + * @generated from enum value: POST_TYPE_POST = 2; + */ + PostType[(PostType["POST"] = 2)] = "POST"; + /** + * @generated from enum value: POST_TYPE_SYSTEM = 3; + */ + PostType[(PostType["SYSTEM"] = 3)] = "SYSTEM"; + /** + * @generated from enum value: POST_TYPE_REDIRECT = 4; + */ + PostType[(PostType["REDIRECT"] = 4)] = "REDIRECT"; + /** + * @generated from enum value: POST_TYPE_MASTER = 5; + */ + PostType[(PostType["MASTER"] = 5)] = "MASTER"; +})(PostType || (PostType = {})); +/** + * @generated from enum blockninja.v1.SystemPageType + */ +var SystemPageType; +(function (SystemPageType) { + /** + * @generated from enum value: SYSTEM_PAGE_TYPE_UNSPECIFIED = 0; + */ + SystemPageType[(SystemPageType["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * @generated from enum value: SYSTEM_PAGE_TYPE_NOT_FOUND = 1; + */ + SystemPageType[(SystemPageType["NOT_FOUND"] = 1)] = "NOT_FOUND"; + /** + * @generated from enum value: SYSTEM_PAGE_TYPE_SERVER_ERROR = 2; + */ + SystemPageType[(SystemPageType["SERVER_ERROR"] = 2)] = "SERVER_ERROR"; + /** + * @generated from enum value: SYSTEM_PAGE_TYPE_MAINTENANCE = 3; + */ + SystemPageType[(SystemPageType["MAINTENANCE"] = 3)] = "MAINTENANCE"; + /** + * @generated from enum value: SYSTEM_PAGE_TYPE_COMING_SOON = 4; + */ + SystemPageType[(SystemPageType["COMING_SOON"] = 4)] = "COMING_SOON"; + /** + * @generated from enum value: SYSTEM_PAGE_TYPE_BLOG_POST_TEMPLATE = 5; + */ + SystemPageType[(SystemPageType["BLOG_POST_TEMPLATE"] = 5)] = "BLOG_POST_TEMPLATE"; + /** + * @generated from enum value: SYSTEM_PAGE_TYPE_AUTHOR = 6; + */ + SystemPageType[(SystemPageType["AUTHOR"] = 6)] = "AUTHOR"; + /** + * @generated from enum value: SYSTEM_PAGE_TYPE_BLOG_INDEX = 7; + */ + SystemPageType[(SystemPageType["BLOG_INDEX"] = 7)] = "BLOG_INDEX"; + /** + * @generated from enum value: SYSTEM_PAGE_TYPE_CATEGORY = 8; + */ + SystemPageType[(SystemPageType["CATEGORY"] = 8)] = "CATEGORY"; +})(SystemPageType || (SystemPageType = {})); +/** + * @generated from enum blockninja.v1.ChildHandling + */ +var ChildHandling; +(function (ChildHandling) { + /** + * @generated from enum value: CHILD_HANDLING_UNSPECIFIED = 0; + */ + ChildHandling[(ChildHandling["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * Delete children with parent + * + * @generated from enum value: CHILD_HANDLING_CASCADE = 1; + */ + ChildHandling[(ChildHandling["CASCADE"] = 1)] = "CASCADE"; + /** + * Move children to root level (null parent_id) + * + * @generated from enum value: CHILD_HANDLING_MOVE_ROOT = 2; + */ + ChildHandling[(ChildHandling["MOVE_ROOT"] = 2)] = "MOVE_ROOT"; +})(ChildHandling || (ChildHandling = {})); +/** + * @generated from service blockninja.v1.PagesService + */ +const PagesService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_pages, 0); + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/ai_optimization.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/ai_optimization.proto. + */ +const file_blockninja_v1_ai_optimization = /*@__PURE__*/ fileDesc( + "CiNibG9ja25pbmphL3YxL2FpX29wdGltaXphdGlvbi5wcm90bxINYmxvY2tuaW5qYS52MSIrCgdGQVFJdGVtEhAKCHF1ZXN0aW9uGAEgASgJEg4KBmFuc3dlchgCIAEoCSIiCiBHZXRBSU9wdGltaXphdGlvbk92ZXJ2aWV3UmVxdWVzdCJcCiFHZXRBSU9wdGltaXphdGlvbk92ZXJ2aWV3UmVzcG9uc2USNwoIb3ZlcnZpZXcYASABKAsyJS5ibG9ja25pbmphLnYxLkFJT3B0aW1pemF0aW9uT3ZlcnZpZXci3wIKFkFJT3B0aW1pemF0aW9uT3ZlcnZpZXcSFQoNb3ZlcmFsbF9zY29yZRgBIAEoBRITCgt0b3RhbF9wYWdlcxgCIAEoBRIXCg90b3RhbF9wdWJsaXNoZWQYAyABKAUSHQoVcGFnZXNfd2l0aF9haV9zdW1tYXJ5GAQgASgFEhYKDnBhZ2VzX3dpdGhfZmFxGAUgASgFEhwKFHBhZ2VzX3dpdGhfa2V5X2ZhY3RzGAYgASgFEhkKEXBhZ2VzX3dpdGhfYXV0aG9yGAcgASgFEh0KFXBhZ2VzX3dpdGhfcG9vcl9zY29yZRgIIAEoBRIYChBsbG1zX3R4dF9lbmFibGVkGAkgASgIEhsKE3NpdGVfZmFxX2NvbmZpZ3VyZWQYCiABKAgSIQoUc2l0ZV9mYXFfYmxvY2tfdGl0bGUYCyABKAlIAIgBAUIXChVfc2l0ZV9mYXFfYmxvY2tfdGl0bGUijQIKHkxpc3RBSU9wdGltaXphdGlvbkF1ZGl0UmVxdWVzdBIMCgRwYWdlGAEgASgFEhEKCXBhZ2Vfc2l6ZRgCIAEoBRIWCglwb3N0X3R5cGUYAyABKAlIAIgBARITCgZzdGF0dXMYBCABKAlIAYgBARISCgVpc3N1ZRgFIAEoCUgCiAEBEhMKBnNlYXJjaBgGIAEoCUgDiAEBEhQKB3NvcnRfYnkYByABKAlIBIgBARIWCglzb3J0X2Rlc2MYCCABKAhIBYgBAUIMCgpfcG9zdF90eXBlQgkKB19zdGF0dXNCCAoGX2lzc3VlQgkKB19zZWFyY2hCCgoIX3NvcnRfYnlCDAoKX3NvcnRfZGVzYyKIAQofTGlzdEFJT3B0aW1pemF0aW9uQXVkaXRSZXNwb25zZRI1CgVpdGVtcxgBIAMoCzImLmJsb2NrbmluamEudjEuQUlPcHRpbWl6YXRpb25BdWRpdEl0ZW0SDQoFdG90YWwYAiABKAUSDAoEcGFnZRgDIAEoBRIRCglwYWdlX3NpemUYBCABKAUirAUKF0FJT3B0aW1pemF0aW9uQXVkaXRJdGVtEgoKAmlkGAEgASgJEg0KBXRpdGxlGAIgASgJEgwKBHNsdWcYAyABKAkSKgoJcG9zdF90eXBlGAQgASgOMhcuYmxvY2tuaW5qYS52MS5Qb3N0VHlwZRIpCgZzdGF0dXMYBSABKA4yGS5ibG9ja25pbmphLnYxLlBhZ2VTdGF0dXMSFgoOaGFzX2FpX3N1bW1hcnkYBiABKAgSDwoHaGFzX2ZhcRgHIAEoCBIVCg1oYXNfa2V5X2ZhY3RzGAggASgIEhIKCmhhc19hdXRob3IYCSABKAgSGQoRc2hvd19sYXN0X3VwZGF0ZWQYCiABKAgSFwoKYWlfc3VtbWFyeRgLIAEoCUgAiAEBEhEKCWZhcV9jb3VudBgMIAEoBRIXCg9rZXlfZmFjdHNfY291bnQYDSABKAUSGAoLYXV0aG9yX25hbWUYDiABKAlIAYgBARIfChJhaV9yZWFkaW5lc3Nfc2NvcmUYDyABKAVIAogBARIYCgthbmFseXplZF9hdBgQIAEoCUgDiAEBEhcKCndvcmRfY291bnQYESABKAVIBIgBARIpCglmYXFfaXRlbXMYEiADKAsyFi5ibG9ja25pbmphLnYxLkZBUUl0ZW0SEQoJa2V5X2ZhY3RzGBMgAygJEhkKDGZhcV9ibG9ja19pZBgUIAEoCUgFiAEBEhwKD2ZhcV9ibG9ja190aXRsZRgVIAEoCUgGiAEBQg0KC19haV9zdW1tYXJ5Qg4KDF9hdXRob3JfbmFtZUIVChNfYWlfcmVhZGluZXNzX3Njb3JlQg4KDF9hbmFseXplZF9hdEINCgtfd29yZF9jb3VudEIPCg1fZmFxX2Jsb2NrX2lkQhIKEF9mYXFfYmxvY2tfdGl0bGUiMAodQW5hbHl6ZVBhZ2VBSVJlYWRpbmVzc1JlcXVlc3QSDwoHcGFnZV9pZBgBIAEoCSJSCh5BbmFseXplUGFnZUFJUmVhZGluZXNzUmVzcG9uc2USMAoGcmVzdWx0GAEgASgLMiAuYmxvY2tuaW5qYS52MS5BSVJlYWRpbmVzc1Jlc3VsdCKFAQoRQUlSZWFkaW5lc3NSZXN1bHQSDwoHcGFnZV9pZBgBIAEoCRIaChJhaV9yZWFkaW5lc3Nfc2NvcmUYAiABKAUSLwoGY2hlY2tzGAMgAygLMh8uYmxvY2tuaW5qYS52MS5BSVJlYWRpbmVzc0NoZWNrEhIKCndvcmRfY291bnQYBCABKAUiZgoQQUlSZWFkaW5lc3NDaGVjaxINCgVjaGVjaxgBIAEoCRIOCgZzdGF0dXMYAiABKAkSDwoHbWVzc2FnZRgDIAEoCRIOCgZwb2ludHMYBCABKAUSEgoKbWF4X3BvaW50cxgFIAEoBSKTAgofVXBkYXRlUGFnZUFJT3B0aW1pemF0aW9uUmVxdWVzdBIPCgdwYWdlX2lkGAEgASgJEhcKCmFpX3N1bW1hcnkYAiABKAlIAIgBARIpCglmYXFfaXRlbXMYAyADKAsyFi5ibG9ja25pbmphLnYxLkZBUUl0ZW0SEQoJa2V5X2ZhY3RzGAQgAygJEh4KEXNob3dfbGFzdF91cGRhdGVkGAUgASgISAGIAQESGQoMZmFxX2Jsb2NrX2lkGAYgASgJSAKIAQESFwoPY2xlYXJfZmFxX2Jsb2NrGAcgASgIQg0KC19haV9zdW1tYXJ5QhQKEl9zaG93X2xhc3RfdXBkYXRlZEIPCg1fZmFxX2Jsb2NrX2lkIlgKIFVwZGF0ZVBhZ2VBSU9wdGltaXphdGlvblJlc3BvbnNlEjQKBGl0ZW0YASABKAsyJi5ibG9ja25pbmphLnYxLkFJT3B0aW1pemF0aW9uQXVkaXRJdGVtIoIBChhHZW5lcmF0ZUFJU3VtbWFyeVJlcXVlc3QSDwoHcGFnZV9pZBgBIAEoCRISCgpwYWdlX3RpdGxlGAIgASgJEg8KB2NvbnRlbnQYAyABKAkSHAoPZm9jdXNfa2V5cGhyYXNlGAQgASgJSACIAQFCEgoQX2ZvY3VzX2tleXBocmFzZSIvChlHZW5lcmF0ZUFJU3VtbWFyeVJlc3BvbnNlEhIKCmFpX3N1bW1hcnkYASABKAkilgEKHUdlbmVyYXRlRkFRU3VnZ2VzdGlvbnNSZXF1ZXN0Eg8KB3BhZ2VfaWQYASABKAkSEgoKcGFnZV90aXRsZRgCIAEoCRIPCgdjb250ZW50GAMgASgJEhwKD2ZvY3VzX2tleXBocmFzZRgEIAEoCUgAiAEBEg0KBWNvdW50GAUgASgFQhIKEF9mb2N1c19rZXlwaHJhc2UiTQoeR2VuZXJhdGVGQVFTdWdnZXN0aW9uc1Jlc3BvbnNlEisKC3N1Z2dlc3Rpb25zGAEgAygLMhYuYmxvY2tuaW5qYS52MS5GQVFJdGVtIhMKEUdldExMTXNUeHRSZXF1ZXN0IkkKEkdldExMTXNUeHRSZXNwb25zZRIPCgdjb250ZW50GAEgASgJEg8KB2VuYWJsZWQYAiABKAgSEQoJaXNfY3VzdG9tGAMgASgIIhgKFkdlbmVyYXRlTExNc1R4dFJlcXVlc3QiKgoXR2VuZXJhdGVMTE1zVHh0UmVzcG9uc2USDwoHY29udGVudBgBIAEoCSIvChtFeHRyYWN0RkFRc0Zyb21CbG9ja1JlcXVlc3QSEAoIYmxvY2tfaWQYASABKAkiXwocRXh0cmFjdEZBUXNGcm9tQmxvY2tSZXNwb25zZRIkCgRmYXFzGAEgAygLMhYuYmxvY2tuaW5qYS52MS5GQVFJdGVtEhkKEWV4dHJhY3Rpb25fbWV0aG9kGAIgASgJMooIChVBSU9wdGltaXphdGlvblNlcnZpY2USfgoZR2V0QUlPcHRpbWl6YXRpb25PdmVydmlldxIvLmJsb2NrbmluamEudjEuR2V0QUlPcHRpbWl6YXRpb25PdmVydmlld1JlcXVlc3QaMC5ibG9ja25pbmphLnYxLkdldEFJT3B0aW1pemF0aW9uT3ZlcnZpZXdSZXNwb25zZRJ4ChdMaXN0QUlPcHRpbWl6YXRpb25BdWRpdBItLmJsb2NrbmluamEudjEuTGlzdEFJT3B0aW1pemF0aW9uQXVkaXRSZXF1ZXN0Gi4uYmxvY2tuaW5qYS52MS5MaXN0QUlPcHRpbWl6YXRpb25BdWRpdFJlc3BvbnNlEnUKFkFuYWx5emVQYWdlQUlSZWFkaW5lc3MSLC5ibG9ja25pbmphLnYxLkFuYWx5emVQYWdlQUlSZWFkaW5lc3NSZXF1ZXN0Gi0uYmxvY2tuaW5qYS52MS5BbmFseXplUGFnZUFJUmVhZGluZXNzUmVzcG9uc2USewoYVXBkYXRlUGFnZUFJT3B0aW1pemF0aW9uEi4uYmxvY2tuaW5qYS52MS5VcGRhdGVQYWdlQUlPcHRpbWl6YXRpb25SZXF1ZXN0Gi8uYmxvY2tuaW5qYS52MS5VcGRhdGVQYWdlQUlPcHRpbWl6YXRpb25SZXNwb25zZRJmChFHZW5lcmF0ZUFJU3VtbWFyeRInLmJsb2NrbmluamEudjEuR2VuZXJhdGVBSVN1bW1hcnlSZXF1ZXN0GiguYmxvY2tuaW5qYS52MS5HZW5lcmF0ZUFJU3VtbWFyeVJlc3BvbnNlEnUKFkdlbmVyYXRlRkFRU3VnZ2VzdGlvbnMSLC5ibG9ja25pbmphLnYxLkdlbmVyYXRlRkFRU3VnZ2VzdGlvbnNSZXF1ZXN0Gi0uYmxvY2tuaW5qYS52MS5HZW5lcmF0ZUZBUVN1Z2dlc3Rpb25zUmVzcG9uc2USUQoKR2V0TExNc1R4dBIgLmJsb2NrbmluamEudjEuR2V0TExNc1R4dFJlcXVlc3QaIS5ibG9ja25pbmphLnYxLkdldExMTXNUeHRSZXNwb25zZRJgCg9HZW5lcmF0ZUxMTXNUeHQSJS5ibG9ja25pbmphLnYxLkdlbmVyYXRlTExNc1R4dFJlcXVlc3QaJi5ibG9ja25pbmphLnYxLkdlbmVyYXRlTExNc1R4dFJlc3BvbnNlEm8KFEV4dHJhY3RGQVFzRnJvbUJsb2NrEiouYmxvY2tuaW5qYS52MS5FeHRyYWN0RkFRc0Zyb21CbG9ja1JlcXVlc3QaKy5ibG9ja25pbmphLnYxLkV4dHJhY3RGQVFzRnJvbUJsb2NrUmVzcG9uc2VCyQEKEWNvbS5ibG9ja25pbmphLnYxQhNBaU9wdGltaXphdGlvblByb3RvUAFaSmdpdC5kZXYuYWxleGR1bm1vdy5jb20vYmxvY2svbmluamEvaW50ZXJuYWwvYXBpL2Jsb2NrbmluamEvdjE7YmxvY2tuaW5qYXYxogIDQlhYqgINQmxvY2tuaW5qYS5WMcoCDUJsb2NrbmluamFcVjHiAhlCbG9ja25pbmphXFYxXEdQQk1ldGFkYXRh6gIOQmxvY2tuaW5qYTo6VjFiBnByb3RvMw", + [file_blockninja_v1_pages], +); +/** + * AIOptimizationService provides AI citation readiness monitoring and optimization + * + * @generated from service blockninja.v1.AIOptimizationService + */ +const AIOptimizationService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_ai_optimization, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/ai_optimization.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Get overall AI optimization statistics + * + * @generated from rpc blockninja.v1.AIOptimizationService.GetAIOptimizationOverview + */ +AIOptimizationService.method.getAIOptimizationOverview; +/** + * List pages with AI optimization audit data + * + * @generated from rpc blockninja.v1.AIOptimizationService.ListAIOptimizationAudit + */ +AIOptimizationService.method.listAIOptimizationAudit; +/** + * Analyze AI readiness for a specific page + * + * @generated from rpc blockninja.v1.AIOptimizationService.AnalyzePageAIReadiness + */ +AIOptimizationService.method.analyzePageAIReadiness; +/** + * Update AI optimization fields for a page + * + * @generated from rpc blockninja.v1.AIOptimizationService.UpdatePageAIOptimization + */ +AIOptimizationService.method.updatePageAIOptimization; +/** + * Generate AI summary using AI provider + * + * @generated from rpc blockninja.v1.AIOptimizationService.GenerateAISummary + */ +AIOptimizationService.method.generateAISummary; +/** + * Generate FAQ suggestions using AI provider + * + * @generated from rpc blockninja.v1.AIOptimizationService.GenerateFAQSuggestions + */ +AIOptimizationService.method.generateFAQSuggestions; +/** + * Get llms.txt content (for preview) + * + * @generated from rpc blockninja.v1.AIOptimizationService.GetLLMsTxt + */ +AIOptimizationService.method.getLLMsTxt; +/** + * Generate llms.txt from site structure + * + * @generated from rpc blockninja.v1.AIOptimizationService.GenerateLLMsTxt + */ +AIOptimizationService.method.generateLLMsTxt; +/** + * Extract FAQ items from a block's content + * + * @generated from rpc blockninja.v1.AIOptimizationService.ExtractFAQsFromBlock + */ +AIOptimizationService.method.extractFAQsFromBlock; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/analytics.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/analytics.proto. + */ +const file_blockninja_v1_analytics = /*@__PURE__*/ fileDesc( + "Ch1ibG9ja25pbmphL3YxL2FuYWx5dGljcy5wcm90bxINYmxvY2tuaW5qYS52MSK9AQoYQW5hbHl0aWNzVGltZVJhbmdlRmlsdGVyEjEKBnByZXNldBgBIAEoDjIhLmJsb2NrbmluamEudjEuQW5hbHl0aWNzVGltZVJhbmdlEi4KBXN0YXJ0GAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgAiAEBEiwKA2VuZBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIAYgBAUIICgZfc3RhcnRCBgoEX2VuZCKMAQobR2V0QW5hbHl0aWNzT3ZlcnZpZXdSZXF1ZXN0EjsKCnRpbWVfcmFuZ2UYASABKAsyJy5ibG9ja25pbmphLnYxLkFuYWx5dGljc1RpbWVSYW5nZUZpbHRlchIaChJpbmNsdWRlX2NvbXBhcmlzb24YAiABKAgSFAoMZXhjbHVkZV9ib3RzGAMgASgIIuQDChxHZXRBbmFseXRpY3NPdmVydmlld1Jlc3BvbnNlEhEKCXBhZ2V2aWV3cxgBIAEoAxIXCg91bmlxdWVfdmlzaXRvcnMYAiABKAMSEAoIc2Vzc2lvbnMYAyABKAMSHwoScGFnZXZpZXdzX3ByZXZpb3VzGAQgASgDSACIAQESJQoYdW5pcXVlX3Zpc2l0b3JzX3ByZXZpb3VzGAUgASgDSAGIAQESHgoRc2Vzc2lvbnNfcHJldmlvdXMYBiABKANIAogBARIlChhwYWdldmlld3NfY2hhbmdlX3BlcmNlbnQYByABKAFIA4gBARIrCh51bmlxdWVfdmlzaXRvcnNfY2hhbmdlX3BlcmNlbnQYCCABKAFIBIgBARIkChdzZXNzaW9uc19jaGFuZ2VfcGVyY2VudBgJIAEoAUgFiAEBQhUKE19wYWdldmlld3NfcHJldmlvdXNCGwoZX3VuaXF1ZV92aXNpdG9yc19wcmV2aW91c0IUChJfc2Vzc2lvbnNfcHJldmlvdXNCGwoZX3BhZ2V2aWV3c19jaGFuZ2VfcGVyY2VudEIhCh9fdW5pcXVlX3Zpc2l0b3JzX2NoYW5nZV9wZXJjZW50QhoKGF9zZXNzaW9uc19jaGFuZ2VfcGVyY2VudCJ2ChJHZXRUb3BQYWdlc1JlcXVlc3QSOwoKdGltZV9yYW5nZRgBIAEoCzInLmJsb2NrbmluamEudjEuQW5hbHl0aWNzVGltZVJhbmdlRmlsdGVyEg0KBWxpbWl0GAIgASgFEhQKDGV4Y2x1ZGVfYm90cxgDIAEoCCI/CgdUb3BQYWdlEgwKBHBhdGgYASABKAkSDQoFdmlld3MYAiABKAMSFwoPdW5pcXVlX3Zpc2l0b3JzGAMgASgDIjwKE0dldFRvcFBhZ2VzUmVzcG9uc2USJQoFcGFnZXMYASADKAsyFi5ibG9ja25pbmphLnYxLlRvcFBhZ2UiegoWR2V0VG9wUmVmZXJyZXJzUmVxdWVzdBI7Cgp0aW1lX3JhbmdlGAEgASgLMicuYmxvY2tuaW5qYS52MS5BbmFseXRpY3NUaW1lUmFuZ2VGaWx0ZXISDQoFbGltaXQYAiABKAUSFAoMZXhjbHVkZV9ib3RzGAMgASgIIiwKC1RvcFJlZmVycmVyEg4KBmRvbWFpbhgBIAEoCRINCgVjb3VudBgCIAEoAyJIChdHZXRUb3BSZWZlcnJlcnNSZXNwb25zZRItCglyZWZlcnJlcnMYASADKAsyGi5ibG9ja25pbmphLnYxLlRvcFJlZmVycmVyInoKFkdldFRvcENhbXBhaWduc1JlcXVlc3QSOwoKdGltZV9yYW5nZRgBIAEoCzInLmJsb2NrbmluamEudjEuQW5hbHl0aWNzVGltZVJhbmdlRmlsdGVyEg0KBWxpbWl0GAIgASgFEhQKDGV4Y2x1ZGVfYm90cxgDIAEoCCJOCgtUb3BDYW1wYWlnbhIOCgZzb3VyY2UYASABKAkSDgoGbWVkaXVtGAIgASgJEhAKCGNhbXBhaWduGAMgASgJEg0KBWNvdW50GAQgASgDIkgKF0dldFRvcENhbXBhaWduc1Jlc3BvbnNlEi0KCWNhbXBhaWducxgBIAMoCzIaLmJsb2NrbmluamEudjEuVG9wQ2FtcGFpZ24iegoWR2V0VG9wQ291bnRyaWVzUmVxdWVzdBI7Cgp0aW1lX3JhbmdlGAEgASgLMicuYmxvY2tuaW5qYS52MS5BbmFseXRpY3NUaW1lUmFuZ2VGaWx0ZXISDQoFbGltaXQYAiABKAUSFAoMZXhjbHVkZV9ib3RzGAMgASgIIjcKClRvcENvdW50cnkSDAoEY29kZRgBIAEoCRIMCgRuYW1lGAIgASgJEg0KBWNvdW50GAMgASgDIkcKF0dldFRvcENvdW50cmllc1Jlc3BvbnNlEiwKCWNvdW50cmllcxgBIAMoCzIZLmJsb2NrbmluamEudjEuVG9wQ291bnRyeSJuChlHZXREZXZpY2VCcmVha2Rvd25SZXF1ZXN0EjsKCnRpbWVfcmFuZ2UYASABKAsyJy5ibG9ja25pbmphLnYxLkFuYWx5dGljc1RpbWVSYW5nZUZpbHRlchIUCgxleGNsdWRlX2JvdHMYAiABKAgiRQoLRGV2aWNlQ291bnQSEwoLZGV2aWNlX3R5cGUYASABKAkSDQoFY291bnQYAiABKAMSEgoKcGVyY2VudGFnZRgDIAEoASJJChpHZXREZXZpY2VCcmVha2Rvd25SZXNwb25zZRIrCgdkZXZpY2VzGAEgAygLMhouYmxvY2tuaW5qYS52MS5EZXZpY2VDb3VudCJ+ChpHZXRCcm93c2VyQnJlYWtkb3duUmVxdWVzdBI7Cgp0aW1lX3JhbmdlGAEgASgLMicuYmxvY2tuaW5qYS52MS5BbmFseXRpY3NUaW1lUmFuZ2VGaWx0ZXISDQoFbGltaXQYAiABKAUSFAoMZXhjbHVkZV9ib3RzGAMgASgIIkkKDEJyb3dzZXJDb3VudBIWCg5icm93c2VyX2ZhbWlseRgBIAEoCRINCgVjb3VudBgCIAEoAxISCgpwZXJjZW50YWdlGAMgASgBIkwKG0dldEJyb3dzZXJCcmVha2Rvd25SZXNwb25zZRItCghicm93c2VycxgBIAMoCzIbLmJsb2NrbmluamEudjEuQnJvd3NlckNvdW50InwKGEdldFRvcFNlYXJjaFRlcm1zUmVxdWVzdBI7Cgp0aW1lX3JhbmdlGAEgASgLMicuYmxvY2tuaW5qYS52MS5BbmFseXRpY3NUaW1lUmFuZ2VGaWx0ZXISDQoFbGltaXQYAiABKAUSFAoMZXhjbHVkZV9ib3RzGAMgASgIIiwKDVRvcFNlYXJjaFRlcm0SDAoEdGVybRgBIAEoCRINCgVjb3VudBgCIAEoAyJIChlHZXRUb3BTZWFyY2hUZXJtc1Jlc3BvbnNlEisKBXRlcm1zGAEgAygLMhwuYmxvY2tuaW5qYS52MS5Ub3BTZWFyY2hUZXJtIocBCh1HZXRQYWdldmlld3NUaW1lc2VyaWVzUmVxdWVzdBI7Cgp0aW1lX3JhbmdlGAEgASgLMicuYmxvY2tuaW5qYS52MS5BbmFseXRpY3NUaW1lUmFuZ2VGaWx0ZXISEwoLZ3JhbnVsYXJpdHkYAiABKAkSFAoMZXhjbHVkZV9ib3RzGAMgASgIImAKD1RpbWVzZXJpZXNQb2ludBIoCgR0aW1lGAEgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIRCglwYWdldmlld3MYAiABKAMSEAoIdmlzaXRvcnMYAyABKAMiUAoeR2V0UGFnZXZpZXdzVGltZXNlcmllc1Jlc3BvbnNlEi4KBnBvaW50cxgBIAMoCzIeLmJsb2NrbmluamEudjEuVGltZXNlcmllc1BvaW50Ih4KHEdldEFjdGl2ZVZpc2l0b3JDb3VudFJlcXVlc3QiLgodR2V0QWN0aXZlVmlzaXRvckNvdW50UmVzcG9uc2USDQoFY291bnQYASABKAMifwoXR2V0UGFnZUFuYWx5dGljc1JlcXVlc3QSEQoJcGFnZV9wYXRoGAEgASgJEjsKCnRpbWVfcmFuZ2UYAiABKAsyJy5ibG9ja25pbmphLnYxLkFuYWx5dGljc1RpbWVSYW5nZUZpbHRlchIUCgxleGNsdWRlX2JvdHMYAyABKAgiWAoYR2V0UGFnZUFuYWx5dGljc1Jlc3BvbnNlEhEKCXBhZ2V2aWV3cxgBIAEoAxIXCg91bmlxdWVfdmlzaXRvcnMYAiABKAMSEAoIc2Vzc2lvbnMYAyABKAMidgohR2V0VHJhY2tpbmdTb3VyY2VCcmVha2Rvd25SZXF1ZXN0EjsKCnRpbWVfcmFuZ2UYASABKAsyJy5ibG9ja25pbmphLnYxLkFuYWx5dGljc1RpbWVSYW5nZUZpbHRlchIUCgxleGNsdWRlX2JvdHMYAiABKAgicgoiR2V0VHJhY2tpbmdTb3VyY2VCcmVha2Rvd25SZXNwb25zZRIUCgxjbGllbnRfY291bnQYASABKAMSGQoRc2VydmVyX29ubHlfY291bnQYAiABKAMSGwoTc2VydmVyX29ubHlfcGVyY2VudBgDIAEoASKaAQoJSWdub3JlZElQEgoKAmlkGAEgASgJEhIKCmlwX2FkZHJlc3MYAiABKAkSDQoFbGFiZWwYAyABKAkSLgoKY3JlYXRlZF9hdBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFQoNY3JlYXRlZF9ieV9pZBgFIAEoCRIXCg9jcmVhdGVkX2J5X25hbWUYBiABKAkiFwoVTGlzdElnbm9yZWRJUHNSZXF1ZXN0IkcKFkxpc3RJZ25vcmVkSVBzUmVzcG9uc2USLQoLaWdub3JlZF9pcHMYASADKAsyGC5ibG9ja25pbmphLnYxLklnbm9yZWRJUCI7ChZDcmVhdGVJZ25vcmVkSVBSZXF1ZXN0EhIKCmlwX2FkZHJlc3MYASABKAkSDQoFbGFiZWwYAiABKAkiRwoXQ3JlYXRlSWdub3JlZElQUmVzcG9uc2USLAoKaWdub3JlZF9pcBgBIAEoCzIYLmJsb2NrbmluamEudjEuSWdub3JlZElQIiQKFkRlbGV0ZUlnbm9yZWRJUFJlcXVlc3QSCgoCaWQYASABKAkiGQoXRGVsZXRlSWdub3JlZElQUmVzcG9uc2UiEAoOR2V0TXlJUFJlcXVlc3QiOQoPR2V0TXlJUFJlc3BvbnNlEhIKCmlwX2FkZHJlc3MYASABKAkSEgoKaXNfaWdub3JlZBgCIAEoCCI/ChVSZXNldEFuYWx5dGljc1JlcXVlc3QSFAoMY29uZmlybWF0aW9uGAEgASgJEhAKCHBhc3N3b3JkGAIgASgJImMKFlJlc2V0QW5hbHl0aWNzUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCBIWCg5ldmVudHNfZGVsZXRlZBgCIAEoAxIgChhkYWlseV9hZ2dyZWdhdGVzX2RlbGV0ZWQYAyABKAMqhAIKEkFuYWx5dGljc1RpbWVSYW5nZRIkCiBBTkFMWVRJQ1NfVElNRV9SQU5HRV9VTlNQRUNJRklFRBAAEh4KGkFOQUxZVElDU19USU1FX1JBTkdFX1RPREFZEAESIgoeQU5BTFlUSUNTX1RJTUVfUkFOR0VfWUVTVEVSREFZEAISHwobQU5BTFlUSUNTX1RJTUVfUkFOR0VfN19EQVlTEAMSIAocQU5BTFlUSUNTX1RJTUVfUkFOR0VfMzBfREFZUxAEEiAKHEFOQUxZVElDU19USU1FX1JBTkdFXzkwX0RBWVMQBRIfChtBTkFMWVRJQ1NfVElNRV9SQU5HRV9DVVNUT00QBjLgDQoQQW5hbHl0aWNzU2VydmljZRJvChRHZXRBbmFseXRpY3NPdmVydmlldxIqLmJsb2NrbmluamEudjEuR2V0QW5hbHl0aWNzT3ZlcnZpZXdSZXF1ZXN0GisuYmxvY2tuaW5qYS52MS5HZXRBbmFseXRpY3NPdmVydmlld1Jlc3BvbnNlElQKC0dldFRvcFBhZ2VzEiEuYmxvY2tuaW5qYS52MS5HZXRUb3BQYWdlc1JlcXVlc3QaIi5ibG9ja25pbmphLnYxLkdldFRvcFBhZ2VzUmVzcG9uc2USYAoPR2V0VG9wUmVmZXJyZXJzEiUuYmxvY2tuaW5qYS52MS5HZXRUb3BSZWZlcnJlcnNSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5HZXRUb3BSZWZlcnJlcnNSZXNwb25zZRJgCg9HZXRUb3BDYW1wYWlnbnMSJS5ibG9ja25pbmphLnYxLkdldFRvcENhbXBhaWduc1JlcXVlc3QaJi5ibG9ja25pbmphLnYxLkdldFRvcENhbXBhaWduc1Jlc3BvbnNlEmAKD0dldFRvcENvdW50cmllcxIlLmJsb2NrbmluamEudjEuR2V0VG9wQ291bnRyaWVzUmVxdWVzdBomLmJsb2NrbmluamEudjEuR2V0VG9wQ291bnRyaWVzUmVzcG9uc2USaQoSR2V0RGV2aWNlQnJlYWtkb3duEiguYmxvY2tuaW5qYS52MS5HZXREZXZpY2VCcmVha2Rvd25SZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5HZXREZXZpY2VCcmVha2Rvd25SZXNwb25zZRJsChNHZXRCcm93c2VyQnJlYWtkb3duEikuYmxvY2tuaW5qYS52MS5HZXRCcm93c2VyQnJlYWtkb3duUmVxdWVzdBoqLmJsb2NrbmluamEudjEuR2V0QnJvd3NlckJyZWFrZG93blJlc3BvbnNlEmYKEUdldFRvcFNlYXJjaFRlcm1zEicuYmxvY2tuaW5qYS52MS5HZXRUb3BTZWFyY2hUZXJtc1JlcXVlc3QaKC5ibG9ja25pbmphLnYxLkdldFRvcFNlYXJjaFRlcm1zUmVzcG9uc2USdQoWR2V0UGFnZXZpZXdzVGltZXNlcmllcxIsLmJsb2NrbmluamEudjEuR2V0UGFnZXZpZXdzVGltZXNlcmllc1JlcXVlc3QaLS5ibG9ja25pbmphLnYxLkdldFBhZ2V2aWV3c1RpbWVzZXJpZXNSZXNwb25zZRJyChVHZXRBY3RpdmVWaXNpdG9yQ291bnQSKy5ibG9ja25pbmphLnYxLkdldEFjdGl2ZVZpc2l0b3JDb3VudFJlcXVlc3QaLC5ibG9ja25pbmphLnYxLkdldEFjdGl2ZVZpc2l0b3JDb3VudFJlc3BvbnNlEmMKEEdldFBhZ2VBbmFseXRpY3MSJi5ibG9ja25pbmphLnYxLkdldFBhZ2VBbmFseXRpY3NSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5HZXRQYWdlQW5hbHl0aWNzUmVzcG9uc2USgQEKGkdldFRyYWNraW5nU291cmNlQnJlYWtkb3duEjAuYmxvY2tuaW5qYS52MS5HZXRUcmFja2luZ1NvdXJjZUJyZWFrZG93blJlcXVlc3QaMS5ibG9ja25pbmphLnYxLkdldFRyYWNraW5nU291cmNlQnJlYWtkb3duUmVzcG9uc2USXQoOTGlzdElnbm9yZWRJUHMSJC5ibG9ja25pbmphLnYxLkxpc3RJZ25vcmVkSVBzUmVxdWVzdBolLmJsb2NrbmluamEudjEuTGlzdElnbm9yZWRJUHNSZXNwb25zZRJgCg9DcmVhdGVJZ25vcmVkSVASJS5ibG9ja25pbmphLnYxLkNyZWF0ZUlnbm9yZWRJUFJlcXVlc3QaJi5ibG9ja25pbmphLnYxLkNyZWF0ZUlnbm9yZWRJUFJlc3BvbnNlEmAKD0RlbGV0ZUlnbm9yZWRJUBIlLmJsb2NrbmluamEudjEuRGVsZXRlSWdub3JlZElQUmVxdWVzdBomLmJsb2NrbmluamEudjEuRGVsZXRlSWdub3JlZElQUmVzcG9uc2USSAoHR2V0TXlJUBIdLmJsb2NrbmluamEudjEuR2V0TXlJUFJlcXVlc3QaHi5ibG9ja25pbmphLnYxLkdldE15SVBSZXNwb25zZRJdCg5SZXNldEFuYWx5dGljcxIkLmJsb2NrbmluamEudjEuUmVzZXRBbmFseXRpY3NSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5SZXNldEFuYWx5dGljc1Jlc3BvbnNlQsQBChFjb20uYmxvY2tuaW5qYS52MUIOQW5hbHl0aWNzUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_google_protobuf_timestamp], +); +/** + * @generated from enum blockninja.v1.AnalyticsTimeRange + */ +var AnalyticsTimeRange; +(function (AnalyticsTimeRange) { + /** + * @generated from enum value: ANALYTICS_TIME_RANGE_UNSPECIFIED = 0; + */ + AnalyticsTimeRange[(AnalyticsTimeRange["ANALYTICS_TIME_RANGE_UNSPECIFIED"] = 0)] = "ANALYTICS_TIME_RANGE_UNSPECIFIED"; + /** + * @generated from enum value: ANALYTICS_TIME_RANGE_TODAY = 1; + */ + AnalyticsTimeRange[(AnalyticsTimeRange["ANALYTICS_TIME_RANGE_TODAY"] = 1)] = "ANALYTICS_TIME_RANGE_TODAY"; + /** + * @generated from enum value: ANALYTICS_TIME_RANGE_YESTERDAY = 2; + */ + AnalyticsTimeRange[(AnalyticsTimeRange["ANALYTICS_TIME_RANGE_YESTERDAY"] = 2)] = "ANALYTICS_TIME_RANGE_YESTERDAY"; + /** + * @generated from enum value: ANALYTICS_TIME_RANGE_7_DAYS = 3; + */ + AnalyticsTimeRange[(AnalyticsTimeRange["ANALYTICS_TIME_RANGE_7_DAYS"] = 3)] = "ANALYTICS_TIME_RANGE_7_DAYS"; + /** + * @generated from enum value: ANALYTICS_TIME_RANGE_30_DAYS = 4; + */ + AnalyticsTimeRange[(AnalyticsTimeRange["ANALYTICS_TIME_RANGE_30_DAYS"] = 4)] = "ANALYTICS_TIME_RANGE_30_DAYS"; + /** + * @generated from enum value: ANALYTICS_TIME_RANGE_90_DAYS = 5; + */ + AnalyticsTimeRange[(AnalyticsTimeRange["ANALYTICS_TIME_RANGE_90_DAYS"] = 5)] = "ANALYTICS_TIME_RANGE_90_DAYS"; + /** + * @generated from enum value: ANALYTICS_TIME_RANGE_CUSTOM = 6; + */ + AnalyticsTimeRange[(AnalyticsTimeRange["ANALYTICS_TIME_RANGE_CUSTOM"] = 6)] = "ANALYTICS_TIME_RANGE_CUSTOM"; +})(AnalyticsTimeRange || (AnalyticsTimeRange = {})); +/** + * AnalyticsService provides analytics data for the admin dashboard + * + * @generated from service blockninja.v1.AnalyticsService + */ +const AnalyticsService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_analytics, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/analytics.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Get overview stats (pageviews, visitors, sessions) + * + * @generated from rpc blockninja.v1.AnalyticsService.GetAnalyticsOverview + */ +AnalyticsService.method.getAnalyticsOverview; +/** + * Get top pages by views + * + * @generated from rpc blockninja.v1.AnalyticsService.GetTopPages + */ +AnalyticsService.method.getTopPages; +/** + * Get top referrers + * + * @generated from rpc blockninja.v1.AnalyticsService.GetTopReferrers + */ +AnalyticsService.method.getTopReferrers; +/** + * Get top campaigns + * + * @generated from rpc blockninja.v1.AnalyticsService.GetTopCampaigns + */ +AnalyticsService.method.getTopCampaigns; +/** + * Get top countries + * + * @generated from rpc blockninja.v1.AnalyticsService.GetTopCountries + */ +AnalyticsService.method.getTopCountries; +/** + * Get device breakdown + * + * @generated from rpc blockninja.v1.AnalyticsService.GetDeviceBreakdown + */ +AnalyticsService.method.getDeviceBreakdown; +/** + * Get browser breakdown + * + * @generated from rpc blockninja.v1.AnalyticsService.GetBrowserBreakdown + */ +AnalyticsService.method.getBrowserBreakdown; +/** + * Get top search terms + * + * @generated from rpc blockninja.v1.AnalyticsService.GetTopSearchTerms + */ +AnalyticsService.method.getTopSearchTerms; +/** + * Get pageviews timeseries + * + * @generated from rpc blockninja.v1.AnalyticsService.GetPageviewsTimeseries + */ +AnalyticsService.method.getPageviewsTimeseries; +/** + * Get real-time active visitor count + * + * @generated from rpc blockninja.v1.AnalyticsService.GetActiveVisitorCount + */ +AnalyticsService.method.getActiveVisitorCount; +/** + * Get analytics for a specific page + * + * @generated from rpc blockninja.v1.AnalyticsService.GetPageAnalytics + */ +AnalyticsService.method.getPageAnalytics; +/** + * Get tracking source breakdown (server-only vs client-tracked) + * + * @generated from rpc blockninja.v1.AnalyticsService.GetTrackingSourceBreakdown + */ +AnalyticsService.method.getTrackingSourceBreakdown; +/** + * Ignored IPs - exclude internal traffic from stats + * + * @generated from rpc blockninja.v1.AnalyticsService.ListIgnoredIPs + */ +AnalyticsService.method.listIgnoredIPs; +/** + * @generated from rpc blockninja.v1.AnalyticsService.CreateIgnoredIP + */ +AnalyticsService.method.createIgnoredIP; +/** + * @generated from rpc blockninja.v1.AnalyticsService.DeleteIgnoredIP + */ +AnalyticsService.method.deleteIgnoredIP; +/** + * Get the caller's public IP address (for "Ignore Me" feature) + * + * @generated from rpc blockninja.v1.AnalyticsService.GetMyIP + */ +AnalyticsService.method.getMyIP; +/** + * Reset all analytics data (superadmin only) + * + * @generated from rpc blockninja.v1.AnalyticsService.ResetAnalytics + */ +AnalyticsService.method.resetAnalytics; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/audit.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/audit.proto. + */ +const file_blockninja_v1_audit = /*@__PURE__*/ fileDesc( + "ChlibG9ja25pbmphL3YxL2F1ZGl0LnByb3RvEg1ibG9ja25pbmphLnYxIsgECghBdWRpdExvZxIKCgJpZBgBIAEoCRIVCghhY3Rvcl9pZBgCIAEoCUgAiAEBEhgKC2FjdG9yX2VtYWlsGAMgASgJSAGIAQESKgoGYWN0aW9uGAQgASgOMhouYmxvY2tuaW5qYS52MS5BdWRpdEFjdGlvbhIuCghyZXNvdXJjZRgFIAEoDjIcLmJsb2NrbmluamEudjEuQXVkaXRSZXNvdXJjZRIYCgtyZXNvdXJjZV9pZBgGIAEoCUgCiAEBEhcKCmlwX2FkZHJlc3MYByABKAlIA4gBARIXCgp1c2VyX2FnZW50GAggASgJSASIAQESGwoOcmVxdWVzdF9tZXRob2QYCSABKAlIBYgBARIZCgxyZXF1ZXN0X3BhdGgYCiABKAlIBogBARIZCgxyZXF1ZXN0X2JvZHkYCyABKAlIB4gBARIcCg9yZXNwb25zZV9zdGF0dXMYDCABKAVICIgBARIVCghtZXRhZGF0YRgNIAEoCUgJiAEBEi4KCmNyZWF0ZWRfYXQYDiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgsKCV9hY3Rvcl9pZEIOCgxfYWN0b3JfZW1haWxCDgoMX3Jlc291cmNlX2lkQg0KC19pcF9hZGRyZXNzQg0KC191c2VyX2FnZW50QhEKD19yZXF1ZXN0X21ldGhvZEIPCg1fcmVxdWVzdF9wYXRoQg8KDV9yZXF1ZXN0X2JvZHlCEgoQX3Jlc3BvbnNlX3N0YXR1c0ILCglfbWV0YWRhdGEilQMKFExpc3RBdWRpdExvZ3NSZXF1ZXN0Ei0KCnBhZ2luYXRpb24YASABKAsyGS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb24SFQoIYWN0b3JfaWQYAiABKAlIAIgBARIvCgZhY3Rpb24YAyABKA4yGi5ibG9ja25pbmphLnYxLkF1ZGl0QWN0aW9uSAGIAQESMwoIcmVzb3VyY2UYBCABKA4yHC5ibG9ja25pbmphLnYxLkF1ZGl0UmVzb3VyY2VIAogBARIYCgtyZXNvdXJjZV9pZBgFIAEoCUgDiAEBEjMKCnN0YXJ0X2RhdGUYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSASIAQESMQoIZW5kX2RhdGUYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSAWIAQFCCwoJX2FjdG9yX2lkQgkKB19hY3Rpb25CCwoJX3Jlc291cmNlQg4KDF9yZXNvdXJjZV9pZEINCgtfc3RhcnRfZGF0ZUILCglfZW5kX2RhdGUidQoVTGlzdEF1ZGl0TG9nc1Jlc3BvbnNlEiUKBGxvZ3MYASADKAsyFy5ibG9ja25pbmphLnYxLkF1ZGl0TG9nEjUKCnBhZ2luYXRpb24YAiABKAsyIS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb25SZXNwb25zZSIgChJHZXRBdWRpdExvZ1JlcXVlc3QSCgoCaWQYASABKAkiOwoTR2V0QXVkaXRMb2dSZXNwb25zZRIkCgNsb2cYASABKAsyFy5ibG9ja25pbmphLnYxLkF1ZGl0TG9nKtsCCgtBdWRpdEFjdGlvbhIcChhBVURJVF9BQ1RJT05fVU5TUEVDSUZJRUQQABIeChpBVURJVF9BQ1RJT05fTE9HSU5fU1VDQ0VTUxABEh4KGkFVRElUX0FDVElPTl9MT0dJTl9GQUlMVVJFEAISFwoTQVVESVRfQUNUSU9OX0xPR09VVBADEiAKHEFVRElUX0FDVElPTl9QQVNTV09SRF9DSEFOR0UQBBInCiNBVURJVF9BQ1RJT05fUEFTU1dPUkRfUkVTRVRfUkVRVUVTVBAFEigKJEFVRElUX0FDVElPTl9QQVNTV09SRF9SRVNFVF9DT01QTEVURRAGEhcKE0FVRElUX0FDVElPTl9DUkVBVEUQBxIVChFBVURJVF9BQ1RJT05fUkVBRBAIEhcKE0FVRElUX0FDVElPTl9VUERBVEUQCRIXChNBVURJVF9BQ1RJT05fREVMRVRFEAoqngEKDUF1ZGl0UmVzb3VyY2USHgoaQVVESVRfUkVTT1VSQ0VfVU5TUEVDSUZJRUQQABIXChNBVURJVF9SRVNPVVJDRV9BVVRIEAESHQoZQVVESVRfUkVTT1VSQ0VfQURNSU5fVVNFUhACEhoKFkFVRElUX1JFU09VUkNFX0NPTlRFTlQQAxIZChVBVURJVF9SRVNPVVJDRV9TWVNURU0QBDLAAQoMQXVkaXRTZXJ2aWNlEloKDUxpc3RBdWRpdExvZ3MSIy5ibG9ja25pbmphLnYxLkxpc3RBdWRpdExvZ3NSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5MaXN0QXVkaXRMb2dzUmVzcG9uc2USVAoLR2V0QXVkaXRMb2cSIS5ibG9ja25pbmphLnYxLkdldEF1ZGl0TG9nUmVxdWVzdBoiLmJsb2NrbmluamEudjEuR2V0QXVkaXRMb2dSZXNwb25zZULAAQoRY29tLmJsb2NrbmluamEudjFCCkF1ZGl0UHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_blockninja_v1_common, file_google_protobuf_timestamp], +); +/** + * @generated from enum blockninja.v1.AuditAction + */ +var AuditAction; +(function (AuditAction) { + /** + * @generated from enum value: AUDIT_ACTION_UNSPECIFIED = 0; + */ + AuditAction[(AuditAction["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * @generated from enum value: AUDIT_ACTION_LOGIN_SUCCESS = 1; + */ + AuditAction[(AuditAction["LOGIN_SUCCESS"] = 1)] = "LOGIN_SUCCESS"; + /** + * @generated from enum value: AUDIT_ACTION_LOGIN_FAILURE = 2; + */ + AuditAction[(AuditAction["LOGIN_FAILURE"] = 2)] = "LOGIN_FAILURE"; + /** + * @generated from enum value: AUDIT_ACTION_LOGOUT = 3; + */ + AuditAction[(AuditAction["LOGOUT"] = 3)] = "LOGOUT"; + /** + * @generated from enum value: AUDIT_ACTION_PASSWORD_CHANGE = 4; + */ + AuditAction[(AuditAction["PASSWORD_CHANGE"] = 4)] = "PASSWORD_CHANGE"; + /** + * @generated from enum value: AUDIT_ACTION_PASSWORD_RESET_REQUEST = 5; + */ + AuditAction[(AuditAction["PASSWORD_RESET_REQUEST"] = 5)] = "PASSWORD_RESET_REQUEST"; + /** + * @generated from enum value: AUDIT_ACTION_PASSWORD_RESET_COMPLETE = 6; + */ + AuditAction[(AuditAction["PASSWORD_RESET_COMPLETE"] = 6)] = "PASSWORD_RESET_COMPLETE"; + /** + * @generated from enum value: AUDIT_ACTION_CREATE = 7; + */ + AuditAction[(AuditAction["CREATE"] = 7)] = "CREATE"; + /** + * @generated from enum value: AUDIT_ACTION_READ = 8; + */ + AuditAction[(AuditAction["READ"] = 8)] = "READ"; + /** + * @generated from enum value: AUDIT_ACTION_UPDATE = 9; + */ + AuditAction[(AuditAction["UPDATE"] = 9)] = "UPDATE"; + /** + * @generated from enum value: AUDIT_ACTION_DELETE = 10; + */ + AuditAction[(AuditAction["DELETE"] = 10)] = "DELETE"; +})(AuditAction || (AuditAction = {})); +/** + * @generated from enum blockninja.v1.AuditResource + */ +var AuditResource; +(function (AuditResource) { + /** + * @generated from enum value: AUDIT_RESOURCE_UNSPECIFIED = 0; + */ + AuditResource[(AuditResource["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * @generated from enum value: AUDIT_RESOURCE_AUTH = 1; + */ + AuditResource[(AuditResource["AUTH"] = 1)] = "AUTH"; + /** + * @generated from enum value: AUDIT_RESOURCE_ADMIN_USER = 2; + */ + AuditResource[(AuditResource["ADMIN_USER"] = 2)] = "ADMIN_USER"; + /** + * @generated from enum value: AUDIT_RESOURCE_CONTENT = 3; + */ + AuditResource[(AuditResource["CONTENT"] = 3)] = "CONTENT"; + /** + * @generated from enum value: AUDIT_RESOURCE_SYSTEM = 4; + */ + AuditResource[(AuditResource["SYSTEM"] = 4)] = "SYSTEM"; +})(AuditResource || (AuditResource = {})); +/** + * @generated from service blockninja.v1.AuditService + */ +const AuditService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_audit, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/audit.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * @generated from rpc blockninja.v1.AuditService.ListAuditLogs + */ +AuditService.method.listAuditLogs; +/** + * @generated from rpc blockninja.v1.AuditService.GetAuditLog + */ +AuditService.method.getAuditLog; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/audit_log.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/audit_log.proto. + */ +const file_blockninja_v1_audit_log = /*@__PURE__*/ fileDesc( + "Ch1ibG9ja25pbmphL3YxL2F1ZGl0X2xvZy5wcm90bxINYmxvY2tuaW5qYS52MSLrAgoNQXVkaXRMb2dFbnRyeRIKCgJpZBgBIAEoCRIaCg1hY3Rvcl91c2VyX2lkGAIgASgJSACIAQESEgoKYWN0b3JfdHlwZRgDIAEoCRIOCgZhY3Rpb24YBCABKAkSGgoNdGFyZ2V0X2VudGl0eRgFIAEoCUgBiAEBEhYKCXRhcmdldF9pZBgGIAEoCUgCiAEBEiEKFHJlcXVlc3Rfc3VtbWFyeV9qc29uGAcgASgJSAOIAQESDgoGcmVzdWx0GAggASgJEhoKDWVycm9yX21lc3NhZ2UYCSABKAlIBIgBARIuCgpjcmVhdGVkX2F0GAogASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEIQCg5fYWN0b3JfdXNlcl9pZEIQCg5fdGFyZ2V0X2VudGl0eUIMCgpfdGFyZ2V0X2lkQhcKFV9yZXF1ZXN0X3N1bW1hcnlfanNvbkIQCg5fZXJyb3JfbWVzc2FnZSLbAgoTTGlzdEF1ZGl0TG9nUmVxdWVzdBIVCghhY3Rvcl9pZBgBIAEoCUgAiAEBEhMKBmFjdGlvbhgCIAEoCUgBiAEBEhoKDXRhcmdldF9lbnRpdHkYAyABKAlIAogBARIWCgl0YXJnZXRfaWQYBCABKAlIA4gBARItCgRmcm9tGAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgEiAEBEisKAnRvGAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgFiAEBEhEKBHBhZ2UYByABKAVIBogBARIWCglwYWdlX3NpemUYCCABKAVIB4gBAUILCglfYWN0b3JfaWRCCQoHX2FjdGlvbkIQCg5fdGFyZ2V0X2VudGl0eUIMCgpfdGFyZ2V0X2lkQgcKBV9mcm9tQgUKA190b0IHCgVfcGFnZUIMCgpfcGFnZV9zaXplIlQKFExpc3RBdWRpdExvZ1Jlc3BvbnNlEi0KB2VudHJpZXMYASADKAsyHC5ibG9ja25pbmphLnYxLkF1ZGl0TG9nRW50cnkSDQoFdG90YWwYAiABKAMiJQoXR2V0QXVkaXRMb2dFbnRyeVJlcXVlc3QSCgoCaWQYASABKAkiRwoYR2V0QXVkaXRMb2dFbnRyeVJlc3BvbnNlEisKBWVudHJ5GAEgASgLMhwuYmxvY2tuaW5qYS52MS5BdWRpdExvZ0VudHJ5Ms8BCg9BdWRpdExvZ1NlcnZpY2USVwoMTGlzdEF1ZGl0TG9nEiIuYmxvY2tuaW5qYS52MS5MaXN0QXVkaXRMb2dSZXF1ZXN0GiMuYmxvY2tuaW5qYS52MS5MaXN0QXVkaXRMb2dSZXNwb25zZRJjChBHZXRBdWRpdExvZ0VudHJ5EiYuYmxvY2tuaW5qYS52MS5HZXRBdWRpdExvZ0VudHJ5UmVxdWVzdBonLmJsb2NrbmluamEudjEuR2V0QXVkaXRMb2dFbnRyeVJlc3BvbnNlQsMBChFjb20uYmxvY2tuaW5qYS52MUINQXVkaXRMb2dQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_google_protobuf_timestamp], +); +/** + * @generated from service blockninja.v1.AuditLogService + */ +const AuditLogService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_audit_log, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/audit_log.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * @generated from rpc blockninja.v1.AuditLogService.ListAuditLog + */ +AuditLogService.method.listAuditLog; +/** + * @generated from rpc blockninja.v1.AuditLogService.GetAuditLogEntry + */ +AuditLogService.method.getAuditLogEntry; + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/auth.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * @generated from rpc blockninja.v1.AuthService.Login + */ +AuthService.method.login; +/** + * @generated from rpc blockninja.v1.AuthService.Logout + */ +AuthService.method.logout; +/** + * @generated from rpc blockninja.v1.AuthService.RefreshSession + */ +AuthService.method.refreshSession; +/** + * @generated from rpc blockninja.v1.AuthService.GetCurrentUser + */ +AuthService.method.getCurrentUser; +/** + * @generated from rpc blockninja.v1.AuthService.ListAuthorizedDevices + */ +AuthService.method.listAuthorizedDevices; +/** + * @generated from rpc blockninja.v1.AuthService.RevokeAuthorizedDevice + */ +AuthService.method.revokeAuthorizedDevice; +/** + * @generated from rpc blockninja.v1.AuthService.ChangePassword + */ +AuthService.method.changePassword; +/** + * @generated from rpc blockninja.v1.AuthService.SetLocalPassword + */ +AuthService.method.setLocalPassword; +/** + * @generated from rpc blockninja.v1.AuthService.RequestPasswordReset + */ +AuthService.method.requestPasswordReset; +/** + * @generated from rpc blockninja.v1.AuthService.ResetPassword + */ +AuthService.method.resetPassword; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/authors.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/authors.proto. + */ +const file_blockninja_v1_authors = /*@__PURE__*/ fileDesc( + "ChtibG9ja25pbmphL3YxL2F1dGhvcnMucHJvdG8SDWJsb2NrbmluamEudjEipAUKDUF1dGhvclByb2ZpbGUSCgoCaWQYASABKAkSFQoNYWRtaW5fdXNlcl9pZBgCIAEoCRIUCgxkaXNwbGF5X25hbWUYAyABKAkSDAoEc2x1ZxgEIAEoCRINCgVlbWFpbBgFIAEoCRILCgNiaW8YBiABKAkSEgoKYXZhdGFyX3VybBgHIAEoCRITCgt3ZWJzaXRlX3VybBgIIAEoCRIWCg50d2l0dGVyX2hhbmRsZRgJIAEoCRIUCgxsaW5rZWRpbl91cmwYCiABKAkSEAoIaXNfZ3Vlc3QYCyABKAgSLgoKY3JlYXRlZF9hdBgMIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKdXBkYXRlZF9hdBgNIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASEgoKcG9zdF9jb3VudBgOIAEoBRIRCglqb2JfdGl0bGUYDyABKAkSDwoHY29tcGFueRgQIAEoCRIQCghsb2NhdGlvbhgRIAEoCRIQCgh0aW1lem9uZRgSIAEoCRIQCghwcm9ub3VucxgTIAEoCRIRCglleHBlcnRpc2UYFCADKAkSFwoPc3BlYWtpbmdfdG9waWNzGBUgAygJEhIKCmdpdGh1Yl91cmwYFiABKAkSEwoLeW91dHViZV91cmwYFyABKAkSFQoNaW5zdGFncmFtX3VybBgYIAEoCRItCgxzb2NpYWxfbGlua3MYGSABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0Ei0KDGJpb19kb2N1bWVudBgaIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSEwoLaXNfZmVhdHVyZWQYGyABKAgSGgoSYWxsb3dfY29udGFjdF9mb3JtGBwgASgIIjcKGUxpc3RBdXRob3JQcm9maWxlc1JlcXVlc3QSGgoSaW5jbHVkZV9wb3N0X2NvdW50GAEgASgIIksKGkxpc3RBdXRob3JQcm9maWxlc1Jlc3BvbnNlEi0KB2F1dGhvcnMYASADKAsyHC5ibG9ja25pbmphLnYxLkF1dGhvclByb2ZpbGUiJQoXR2V0QXV0aG9yUHJvZmlsZVJlcXVlc3QSCgoCaWQYASABKAkiSAoYR2V0QXV0aG9yUHJvZmlsZVJlc3BvbnNlEiwKBmF1dGhvchgBIAEoCzIcLmJsb2NrbmluamEudjEuQXV0aG9yUHJvZmlsZSItCh1HZXRBdXRob3JQcm9maWxlQnlTbHVnUmVxdWVzdBIMCgRzbHVnGAEgASgJIk4KHkdldEF1dGhvclByb2ZpbGVCeVNsdWdSZXNwb25zZRIsCgZhdXRob3IYASABKAsyHC5ibG9ja25pbmphLnYxLkF1dGhvclByb2ZpbGUinwQKGkNyZWF0ZUF1dGhvclByb2ZpbGVSZXF1ZXN0EhUKDWFkbWluX3VzZXJfaWQYASABKAkSFAoMZGlzcGxheV9uYW1lGAIgASgJEgwKBHNsdWcYAyABKAkSDQoFZW1haWwYBCABKAkSCwoDYmlvGAUgASgJEhIKCmF2YXRhcl91cmwYBiABKAkSEwoLd2Vic2l0ZV91cmwYByABKAkSFgoOdHdpdHRlcl9oYW5kbGUYCCABKAkSFAoMbGlua2VkaW5fdXJsGAkgASgJEhEKCWpvYl90aXRsZRgKIAEoCRIPCgdjb21wYW55GAsgASgJEhAKCGxvY2F0aW9uGAwgASgJEhAKCHRpbWV6b25lGA0gASgJEhAKCHByb25vdW5zGA4gASgJEhEKCWV4cGVydGlzZRgPIAMoCRIXCg9zcGVha2luZ190b3BpY3MYECADKAkSEgoKZ2l0aHViX3VybBgRIAEoCRITCgt5b3V0dWJlX3VybBgSIAEoCRIVCg1pbnN0YWdyYW1fdXJsGBMgASgJEi0KDHNvY2lhbF9saW5rcxgUIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSLQoMYmlvX2RvY3VtZW50GBUgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBITCgtpc19mZWF0dXJlZBgWIAEoCBIaChJhbGxvd19jb250YWN0X2Zvcm0YFyABKAgiSwobQ3JlYXRlQXV0aG9yUHJvZmlsZVJlc3BvbnNlEiwKBmF1dGhvchgBIAEoCzIcLmJsb2NrbmluamEudjEuQXV0aG9yUHJvZmlsZSKUBAoaVXBkYXRlQXV0aG9yUHJvZmlsZVJlcXVlc3QSCgoCaWQYASABKAkSFAoMZGlzcGxheV9uYW1lGAIgASgJEgwKBHNsdWcYAyABKAkSDQoFZW1haWwYBCABKAkSCwoDYmlvGAUgASgJEhIKCmF2YXRhcl91cmwYBiABKAkSEwoLd2Vic2l0ZV91cmwYByABKAkSFgoOdHdpdHRlcl9oYW5kbGUYCCABKAkSFAoMbGlua2VkaW5fdXJsGAkgASgJEhEKCWpvYl90aXRsZRgKIAEoCRIPCgdjb21wYW55GAsgASgJEhAKCGxvY2F0aW9uGAwgASgJEhAKCHRpbWV6b25lGA0gASgJEhAKCHByb25vdW5zGA4gASgJEhEKCWV4cGVydGlzZRgPIAMoCRIXCg9zcGVha2luZ190b3BpY3MYECADKAkSEgoKZ2l0aHViX3VybBgRIAEoCRITCgt5b3V0dWJlX3VybBgSIAEoCRIVCg1pbnN0YWdyYW1fdXJsGBMgASgJEi0KDHNvY2lhbF9saW5rcxgUIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSLQoMYmlvX2RvY3VtZW50GBUgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBITCgtpc19mZWF0dXJlZBgWIAEoCBIaChJhbGxvd19jb250YWN0X2Zvcm0YFyABKAgiSwobVXBkYXRlQXV0aG9yUHJvZmlsZVJlc3BvbnNlEiwKBmF1dGhvchgBIAEoCzIcLmJsb2NrbmluamEudjEuQXV0aG9yUHJvZmlsZSIoChpEZWxldGVBdXRob3JQcm9maWxlUmVxdWVzdBIKCgJpZBgBIAEoCSIuChtEZWxldGVBdXRob3JQcm9maWxlUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCJJChVHZXRBdXRob3JQb3N0c1JlcXVlc3QSEQoJYXV0aG9yX2lkGAEgASgJEg0KBWxpbWl0GAIgASgFEg4KBm9mZnNldBgDIAEoBSJXChZHZXRBdXRob3JQb3N0c1Jlc3BvbnNlEigKBXBvc3RzGAEgAygLMhkuYmxvY2tuaW5qYS52MS5BdXRob3JQb3N0EhMKC3RvdGFsX2NvdW50GAIgASgFIrIBCgpBdXRob3JQb3N0EgoKAmlkGAEgASgJEg0KBXRpdGxlGAIgASgJEgwKBHNsdWcYAyABKAkSDwoHZXhjZXJwdBgEIAEoCRIaChJmZWF0dXJlZF9pbWFnZV91cmwYBSABKAkSMAoMcHVibGlzaGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIcChRyZWFkaW5nX3RpbWVfbWludXRlcxgHIAEoBSJCChRTZXRQYWdlQXV0aG9yUmVxdWVzdBIPCgdwYWdlX2lkGAEgASgJEhkKEWF1dGhvcl9wcm9maWxlX2lkGAIgASgJIigKFVNldFBhZ2VBdXRob3JSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIIhsKGUdldE15QXV0aG9yUHJvZmlsZVJlcXVlc3QiWgoaR2V0TXlBdXRob3JQcm9maWxlUmVzcG9uc2USLAoGYXV0aG9yGAEgASgLMhwuYmxvY2tuaW5qYS52MS5BdXRob3JQcm9maWxlEg4KBmV4aXN0cxgCIAEoCCJCChxFbnN1cmVNeUF1dGhvclByb2ZpbGVSZXF1ZXN0EhQKDGRpc3BsYXlfbmFtZRgBIAEoCRIMCgRzbHVnGAIgASgJIl4KHUVuc3VyZU15QXV0aG9yUHJvZmlsZVJlc3BvbnNlEiwKBmF1dGhvchgBIAEoCzIcLmJsb2NrbmluamEudjEuQXV0aG9yUHJvZmlsZRIPCgdjcmVhdGVkGAIgASgIMrsICg5BdXRob3JzU2VydmljZRJpChJMaXN0QXV0aG9yUHJvZmlsZXMSKC5ibG9ja25pbmphLnYxLkxpc3RBdXRob3JQcm9maWxlc1JlcXVlc3QaKS5ibG9ja25pbmphLnYxLkxpc3RBdXRob3JQcm9maWxlc1Jlc3BvbnNlEmMKEEdldEF1dGhvclByb2ZpbGUSJi5ibG9ja25pbmphLnYxLkdldEF1dGhvclByb2ZpbGVSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5HZXRBdXRob3JQcm9maWxlUmVzcG9uc2USdQoWR2V0QXV0aG9yUHJvZmlsZUJ5U2x1ZxIsLmJsb2NrbmluamEudjEuR2V0QXV0aG9yUHJvZmlsZUJ5U2x1Z1JlcXVlc3QaLS5ibG9ja25pbmphLnYxLkdldEF1dGhvclByb2ZpbGVCeVNsdWdSZXNwb25zZRJsChNDcmVhdGVBdXRob3JQcm9maWxlEikuYmxvY2tuaW5qYS52MS5DcmVhdGVBdXRob3JQcm9maWxlUmVxdWVzdBoqLmJsb2NrbmluamEudjEuQ3JlYXRlQXV0aG9yUHJvZmlsZVJlc3BvbnNlEmwKE1VwZGF0ZUF1dGhvclByb2ZpbGUSKS5ibG9ja25pbmphLnYxLlVwZGF0ZUF1dGhvclByb2ZpbGVSZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5VcGRhdGVBdXRob3JQcm9maWxlUmVzcG9uc2USbAoTRGVsZXRlQXV0aG9yUHJvZmlsZRIpLmJsb2NrbmluamEudjEuRGVsZXRlQXV0aG9yUHJvZmlsZVJlcXVlc3QaKi5ibG9ja25pbmphLnYxLkRlbGV0ZUF1dGhvclByb2ZpbGVSZXNwb25zZRJdCg5HZXRBdXRob3JQb3N0cxIkLmJsb2NrbmluamEudjEuR2V0QXV0aG9yUG9zdHNSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5HZXRBdXRob3JQb3N0c1Jlc3BvbnNlEloKDVNldFBhZ2VBdXRob3ISIy5ibG9ja25pbmphLnYxLlNldFBhZ2VBdXRob3JSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5TZXRQYWdlQXV0aG9yUmVzcG9uc2USaQoSR2V0TXlBdXRob3JQcm9maWxlEiguYmxvY2tuaW5qYS52MS5HZXRNeUF1dGhvclByb2ZpbGVSZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5HZXRNeUF1dGhvclByb2ZpbGVSZXNwb25zZRJyChVFbnN1cmVNeUF1dGhvclByb2ZpbGUSKy5ibG9ja25pbmphLnYxLkVuc3VyZU15QXV0aG9yUHJvZmlsZVJlcXVlc3QaLC5ibG9ja25pbmphLnYxLkVuc3VyZU15QXV0aG9yUHJvZmlsZVJlc3BvbnNlQsIBChFjb20uYmxvY2tuaW5qYS52MUIMQXV0aG9yc1Byb3RvUAFaSmdpdC5kZXYuYWxleGR1bm1vdy5jb20vYmxvY2svbmluamEvaW50ZXJuYWwvYXBpL2Jsb2NrbmluamEvdjE7YmxvY2tuaW5qYXYxogIDQlhYqgINQmxvY2tuaW5qYS5WMcoCDUJsb2NrbmluamFcVjHiAhlCbG9ja25pbmphXFYxXEdQQk1ldGFkYXRh6gIOQmxvY2tuaW5qYTo6VjFiBnByb3RvMw", + [file_google_protobuf_timestamp, file_google_protobuf_struct], +); +/** + * AuthorsService manages author profiles for blog posts + * + * @generated from service blockninja.v1.AuthorsService + */ +const AuthorsService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_authors, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/authors.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * List all author profiles + * + * @generated from rpc blockninja.v1.AuthorsService.ListAuthorProfiles + */ +AuthorsService.method.listAuthorProfiles; +/** + * Get a single author profile by ID + * + * @generated from rpc blockninja.v1.AuthorsService.GetAuthorProfile + */ +AuthorsService.method.getAuthorProfile; +/** + * Get author profile by slug (for public author pages) + * + * @generated from rpc blockninja.v1.AuthorsService.GetAuthorProfileBySlug + */ +AuthorsService.method.getAuthorProfileBySlug; +/** + * Create a new author profile (guest author or link to admin user) + * + * @generated from rpc blockninja.v1.AuthorsService.CreateAuthorProfile + */ +AuthorsService.method.createAuthorProfile; +/** + * Update an author profile + * + * @generated from rpc blockninja.v1.AuthorsService.UpdateAuthorProfile + */ +AuthorsService.method.updateAuthorProfile; +/** + * Delete an author profile + * + * @generated from rpc blockninja.v1.AuthorsService.DeleteAuthorProfile + */ +AuthorsService.method.deleteAuthorProfile; +/** + * Get posts by author (for author page) + * + * @generated from rpc blockninja.v1.AuthorsService.GetAuthorPosts + */ +AuthorsService.method.getAuthorPosts; +/** + * Set author profile for a page/post + * + * @generated from rpc blockninja.v1.AuthorsService.SetPageAuthor + */ +AuthorsService.method.setPageAuthor; +/** + * Get the current user's author profile + * + * @generated from rpc blockninja.v1.AuthorsService.GetMyAuthorProfile + */ +AuthorsService.method.getMyAuthorProfile; +/** + * Ensure the current user has an author profile (create if doesn't exist) + * + * @generated from rpc blockninja.v1.AuthorsService.EnsureMyAuthorProfile + */ +AuthorsService.method.ensureMyAuthorProfile; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/background_jobs.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/background_jobs.proto. + */ +const file_blockninja_v1_background_jobs = /*@__PURE__*/ fileDesc( + "CiNibG9ja25pbmphL3YxL2JhY2tncm91bmRfam9icy5wcm90bxINYmxvY2tuaW5qYS52MSKlAwoRQmFja2dyb3VuZEpvYkluZm8SCgoCaWQYASABKAkSEwoLaW5zdGFuY2VfaWQYAiABKAkSEAoIam9iX3R5cGUYAyABKAkSJwoGY29uZmlnGAQgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIOCgZzdGF0dXMYBSABKAkSEgoFZXJyb3IYBiABKAlIAIgBARIuCgpjcmVhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIzCgpzdGFydGVkX2F0GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgBiAEBEjUKDGNvbXBsZXRlZF9hdBgJIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIAogBARIYChBwcm9ncmVzc19jdXJyZW50GAogASgFEhYKDnByb2dyZXNzX3RvdGFsGAsgASgFEhgKEHByb2dyZXNzX21lc3NhZ2UYDCABKAlCCAoGX2Vycm9yQg0KC19zdGFydGVkX2F0Qg8KDV9jb21wbGV0ZWRfYXQiXwoZTGlzdEJhY2tncm91bmRKb2JzUmVxdWVzdBIVCghqb2JfdHlwZRgBIAEoCUgAiAEBEhMKBnN0YXR1cxgCIAEoCUgBiAEBQgsKCV9qb2JfdHlwZUIJCgdfc3RhdHVzIkwKGkxpc3RCYWNrZ3JvdW5kSm9ic1Jlc3BvbnNlEi4KBGpvYnMYASADKAsyIC5ibG9ja25pbmphLnYxLkJhY2tncm91bmRKb2JJbmZvIikKF0dldEJhY2tncm91bmRKb2JSZXF1ZXN0Eg4KBmpvYl9pZBgBIAEoCSJJChhHZXRCYWNrZ3JvdW5kSm9iUmVzcG9uc2USLQoDam9iGAEgASgLMiAuYmxvY2tuaW5qYS52MS5CYWNrZ3JvdW5kSm9iSW5mbyJXChpDcmVhdGVCYWNrZ3JvdW5kSm9iUmVxdWVzdBIQCghqb2JfdHlwZRgBIAEoCRInCgZjb25maWcYAiABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0IkwKG0NyZWF0ZUJhY2tncm91bmRKb2JSZXNwb25zZRItCgNqb2IYASABKAsyIC5ibG9ja25pbmphLnYxLkJhY2tncm91bmRKb2JJbmZvIiwKGkNhbmNlbEJhY2tncm91bmRKb2JSZXF1ZXN0Eg4KBmpvYl9pZBgBIAEoCSIwChtDYW5jZWxCYWNrZ3JvdW5kSm9iUmVzcG9uc2USEQoJY2FuY2VsbGVkGAEgASgIIioKGFN0cmVhbUpvYlByb2dyZXNzUmVxdWVzdBIOCgZqb2JfaWQYASABKAkiPgoLSm9iUHJvZ3Jlc3MSDwoHY3VycmVudBgBIAEoBRINCgV0b3RhbBgCIAEoBRIPCgdtZXNzYWdlGAMgASgJMp8EChVCYWNrZ3JvdW5kSm9ic1NlcnZpY2USaQoSTGlzdEJhY2tncm91bmRKb2JzEiguYmxvY2tuaW5qYS52MS5MaXN0QmFja2dyb3VuZEpvYnNSZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5MaXN0QmFja2dyb3VuZEpvYnNSZXNwb25zZRJjChBHZXRCYWNrZ3JvdW5kSm9iEiYuYmxvY2tuaW5qYS52MS5HZXRCYWNrZ3JvdW5kSm9iUmVxdWVzdBonLmJsb2NrbmluamEudjEuR2V0QmFja2dyb3VuZEpvYlJlc3BvbnNlEmwKE0NyZWF0ZUJhY2tncm91bmRKb2ISKS5ibG9ja25pbmphLnYxLkNyZWF0ZUJhY2tncm91bmRKb2JSZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5DcmVhdGVCYWNrZ3JvdW5kSm9iUmVzcG9uc2USbAoTQ2FuY2VsQmFja2dyb3VuZEpvYhIpLmJsb2NrbmluamEudjEuQ2FuY2VsQmFja2dyb3VuZEpvYlJlcXVlc3QaKi5ibG9ja25pbmphLnYxLkNhbmNlbEJhY2tncm91bmRKb2JSZXNwb25zZRJaChFTdHJlYW1Kb2JQcm9ncmVzcxInLmJsb2NrbmluamEudjEuU3RyZWFtSm9iUHJvZ3Jlc3NSZXF1ZXN0GhouYmxvY2tuaW5qYS52MS5Kb2JQcm9ncmVzczABQskBChFjb20uYmxvY2tuaW5qYS52MUITQmFja2dyb3VuZEpvYnNQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_google_protobuf_timestamp, file_google_protobuf_struct], +); +/** + * @generated from service blockninja.v1.BackgroundJobsService + */ +const BackgroundJobsService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_background_jobs, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/background_jobs.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * @generated from rpc blockninja.v1.BackgroundJobsService.ListBackgroundJobs + */ +BackgroundJobsService.method.listBackgroundJobs; +/** + * @generated from rpc blockninja.v1.BackgroundJobsService.GetBackgroundJob + */ +BackgroundJobsService.method.getBackgroundJob; +/** + * @generated from rpc blockninja.v1.BackgroundJobsService.CreateBackgroundJob + */ +BackgroundJobsService.method.createBackgroundJob; +/** + * @generated from rpc blockninja.v1.BackgroundJobsService.CancelBackgroundJob + */ +BackgroundJobsService.method.cancelBackgroundJob; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/backup.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/backup.proto. + */ +const file_blockninja_v1_backup = /*@__PURE__*/ fileDesc( + "ChpibG9ja25pbmphL3YxL2JhY2t1cC5wcm90bxINYmxvY2tuaW5qYS52MSImChJTdGFydEJhY2t1cFJlcXVlc3QSEAoIcGFzc3dvcmQYASABKAkiKAoTU3RhcnRCYWNrdXBSZXNwb25zZRIRCgliYWNrdXBfaWQYASABKAkiFAoSTGlzdEJhY2t1cHNSZXF1ZXN0IkEKE0xpc3RCYWNrdXBzUmVzcG9uc2USKgoHYmFja3VwcxgBIAMoCzIZLmJsb2NrbmluamEudjEuQmFja3VwSW5mbyIoChNEZWxldGVCYWNrdXBSZXF1ZXN0EhEKCWJhY2t1cF9pZBgBIAEoCSInChREZWxldGVCYWNrdXBSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIIioKFURvd25sb2FkQmFja3VwUmVxdWVzdBIRCgliYWNrdXBfaWQYASABKAkiPwoWRG93bmxvYWRCYWNrdXBSZXNwb25zZRITCgtiYWNrdXBfZGF0YRgBIAEoDBIQCghmaWxlbmFtZRgCIAEoCSKPAgoKQmFja3VwSW5mbxIRCgliYWNrdXBfaWQYASABKAkSEAoIZmlsZW5hbWUYAiABKAkSEgoKc2l6ZV9ieXRlcxgDIAEoAxIOCgZzdGF0dXMYBCABKAkSLgoKY3JlYXRlZF9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASMAoMY29tcGxldGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBINCgVlcnJvchgHIAEoCRItCgdzdW1tYXJ5GAggASgLMhwuYmxvY2tuaW5qYS52MS5CYWNrdXBTdW1tYXJ5EhgKEHN0b3JhZ2VfbG9jYXRpb24YCSABKAkiWwoNQmFja3VwU3VtbWFyeRIbChNkYXRhYmFzZV9zaXplX2J5dGVzGAEgASgDEhMKC21lZGlhX2NvdW50GAIgASgFEhgKEHRvdGFsX3NpemVfYnl0ZXMYAyABKAMiPQoUUHJldmlld0JhY2t1cFJlcXVlc3QSEwoLYmFja3VwX2RhdGEYASABKAwSEAoIcGFzc3dvcmQYAiABKAkiyAEKFVByZXZpZXdCYWNrdXBSZXNwb25zZRINCgV2YWxpZBgBIAEoCBINCgVlcnJvchgCIAEoCRIZChFzb3VyY2Vfc2l0ZV90aXRsZRgDIAEoCRIvCgtleHBvcnRlZF9hdBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFgoOZm9ybWF0X3ZlcnNpb24YBSABKAUSLQoHc3VtbWFyeRgGIAEoCzIcLmJsb2NrbmluamEudjEuQmFja3VwU3VtbWFyeSJaChRSZXN0b3JlQmFja3VwUmVxdWVzdBITCgtiYWNrdXBfZGF0YRgBIAEoDBIQCghwYXNzd29yZBgCIAEoCRIbChNjb25maXJtYXRpb25fcGhyYXNlGAMgASgJIkoKFVJlc3RvcmVCYWNrdXBSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEg4KBmVycm9ycxgCIAMoCRIQCgh3YXJuaW5ncxgDIAMoCSKAAwoOQmFja3VwU2NoZWR1bGUSDwoHZW5hYmxlZBgBIAEoCBIyCg1zY2hlZHVsZV90eXBlGAIgASgOMhsuYmxvY2tuaW5qYS52MS5TY2hlZHVsZVR5cGUSFgoOaW50ZXJ2YWxfaG91cnMYAyABKAUSEwoLaG91cl9vZl9kYXkYBCABKAUSEwoLZGF5X29mX3dlZWsYBSABKAUSFAoMZGF5X29mX21vbnRoGAYgASgFEj4KE3N0b3JhZ2VfZGVzdGluYXRpb24YByABKA4yIS5ibG9ja25pbmphLnYxLlN0b3JhZ2VEZXN0aW5hdGlvbhIvCgtuZXh0X3J1bl9hdBgIIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLwoLbGFzdF9ydW5fYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhcKD2xhc3RfcnVuX3N0YXR1cxgKIAEoCRIWCg5sYXN0X3J1bl9lcnJvchgLIAEoCSIaChhHZXRCYWNrdXBTY2hlZHVsZVJlcXVlc3QiTAoZR2V0QmFja3VwU2NoZWR1bGVSZXNwb25zZRIvCghzY2hlZHVsZRgBIAEoCzIdLmJsb2NrbmluamEudjEuQmFja3VwU2NoZWR1bGUiigIKGVNhdmVCYWNrdXBTY2hlZHVsZVJlcXVlc3QSDwoHZW5hYmxlZBgBIAEoCBIyCg1zY2hlZHVsZV90eXBlGAIgASgOMhsuYmxvY2tuaW5qYS52MS5TY2hlZHVsZVR5cGUSFgoOaW50ZXJ2YWxfaG91cnMYAyABKAUSEwoLaG91cl9vZl9kYXkYBCABKAUSEwoLZGF5X29mX3dlZWsYBSABKAUSFAoMZGF5X29mX21vbnRoGAYgASgFEj4KE3N0b3JhZ2VfZGVzdGluYXRpb24YByABKA4yIS5ibG9ja25pbmphLnYxLlN0b3JhZ2VEZXN0aW5hdGlvbhIQCghwYXNzd29yZBgIIAEoCSJtChpTYXZlQmFja3VwU2NoZWR1bGVSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEi8KC25leHRfcnVuX2F0GAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBINCgVlcnJvchgDIAEoCSI9Cg9SZXRlbnRpb25Qb2xpY3kSEQoJa2VlcF9kYXlzGAEgASgFEhcKD21pbmltdW1fYmFja3VwcxgCIAEoBSIbChlHZXRSZXRlbnRpb25Qb2xpY3lSZXF1ZXN0IkwKGkdldFJldGVudGlvblBvbGljeVJlc3BvbnNlEi4KBnBvbGljeRgBIAEoCzIeLmJsb2NrbmluamEudjEuUmV0ZW50aW9uUG9saWN5IkwKGlNhdmVSZXRlbnRpb25Qb2xpY3lSZXF1ZXN0Ei4KBnBvbGljeRgBIAEoCzIeLmJsb2NrbmluamEudjEuUmV0ZW50aW9uUG9saWN5Ii4KG1NhdmVSZXRlbnRpb25Qb2xpY3lSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIInoKGEJhY2t1cE5vdGlmaWNhdGlvbkNvbmZpZxIPCgdlbmFibGVkGAEgASgIEhkKEW5vdGlmeV9vbl9zdWNjZXNzGAIgASgIEhkKEW5vdGlmeV9vbl9mYWlsdXJlGAMgASgIEhcKD3JlY2lwaWVudF9lbWFpbBgEIAEoCSIfCh1HZXRCYWNrdXBOb3RpZmljYXRpb25zUmVxdWVzdCJZCh5HZXRCYWNrdXBOb3RpZmljYXRpb25zUmVzcG9uc2USNwoGY29uZmlnGAEgASgLMicuYmxvY2tuaW5qYS52MS5CYWNrdXBOb3RpZmljYXRpb25Db25maWciWQoeU2F2ZUJhY2t1cE5vdGlmaWNhdGlvbnNSZXF1ZXN0EjcKBmNvbmZpZxgBIAEoCzInLmJsb2NrbmluamEudjEuQmFja3VwTm90aWZpY2F0aW9uQ29uZmlnIjIKH1NhdmVCYWNrdXBOb3RpZmljYXRpb25zUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCIwChtHZXRCYWNrdXBEb3dubG9hZFVybFJlcXVlc3QSEQoJYmFja3VwX2lkGAEgASgJImQKHEdldEJhY2t1cERvd25sb2FkVXJsUmVzcG9uc2USFAoMZG93bmxvYWRfdXJsGAEgASgJEi4KCmV4cGlyZXNfYXQYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIhwKGlJ1blJldGVudGlvbkNsZWFudXBSZXF1ZXN0IlAKG1J1blJldGVudGlvbkNsZWFudXBSZXNwb25zZRIVCg1kZWxldGVkX2NvdW50GAEgASgFEhoKEmRlbGV0ZWRfYmFja3VwX2lkcxgCIAMoCSqVAQoMU2NoZWR1bGVUeXBlEh0KGVNDSEVEVUxFX1RZUEVfVU5TUEVDSUZJRUQQABIYChRTQ0hFRFVMRV9UWVBFX0hPVVJMWRABEhcKE1NDSEVEVUxFX1RZUEVfREFJTFkQAhIYChRTQ0hFRFVMRV9UWVBFX1dFRUtMWRADEhkKFVNDSEVEVUxFX1RZUEVfTU9OVEhMWRAEKpIBChJTdG9yYWdlRGVzdGluYXRpb24SIwofU1RPUkFHRV9ERVNUSU5BVElPTl9VTlNQRUNJRklFRBAAEh0KGVNUT1JBR0VfREVTVElOQVRJT05fTE9DQUwQARIaChZTVE9SQUdFX0RFU1RJTkFUSU9OX1MzEAISHAoYU1RPUkFHRV9ERVNUSU5BVElPTl9CT1RIEAMypwsKDUJhY2t1cFNlcnZpY2USVAoLU3RhcnRCYWNrdXASIS5ibG9ja25pbmphLnYxLlN0YXJ0QmFja3VwUmVxdWVzdBoiLmJsb2NrbmluamEudjEuU3RhcnRCYWNrdXBSZXNwb25zZRJUCgtMaXN0QmFja3VwcxIhLmJsb2NrbmluamEudjEuTGlzdEJhY2t1cHNSZXF1ZXN0GiIuYmxvY2tuaW5qYS52MS5MaXN0QmFja3Vwc1Jlc3BvbnNlElcKDERlbGV0ZUJhY2t1cBIiLmJsb2NrbmluamEudjEuRGVsZXRlQmFja3VwUmVxdWVzdBojLmJsb2NrbmluamEudjEuRGVsZXRlQmFja3VwUmVzcG9uc2USXQoORG93bmxvYWRCYWNrdXASJC5ibG9ja25pbmphLnYxLkRvd25sb2FkQmFja3VwUmVxdWVzdBolLmJsb2NrbmluamEudjEuRG93bmxvYWRCYWNrdXBSZXNwb25zZRJaCg1QcmV2aWV3QmFja3VwEiMuYmxvY2tuaW5qYS52MS5QcmV2aWV3QmFja3VwUmVxdWVzdBokLmJsb2NrbmluamEudjEuUHJldmlld0JhY2t1cFJlc3BvbnNlEloKDVJlc3RvcmVCYWNrdXASIy5ibG9ja25pbmphLnYxLlJlc3RvcmVCYWNrdXBSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5SZXN0b3JlQmFja3VwUmVzcG9uc2USZgoRR2V0QmFja3VwU2NoZWR1bGUSJy5ibG9ja25pbmphLnYxLkdldEJhY2t1cFNjaGVkdWxlUmVxdWVzdBooLmJsb2NrbmluamEudjEuR2V0QmFja3VwU2NoZWR1bGVSZXNwb25zZRJpChJTYXZlQmFja3VwU2NoZWR1bGUSKC5ibG9ja25pbmphLnYxLlNhdmVCYWNrdXBTY2hlZHVsZVJlcXVlc3QaKS5ibG9ja25pbmphLnYxLlNhdmVCYWNrdXBTY2hlZHVsZVJlc3BvbnNlEmkKEkdldFJldGVudGlvblBvbGljeRIoLmJsb2NrbmluamEudjEuR2V0UmV0ZW50aW9uUG9saWN5UmVxdWVzdBopLmJsb2NrbmluamEudjEuR2V0UmV0ZW50aW9uUG9saWN5UmVzcG9uc2USbAoTU2F2ZVJldGVudGlvblBvbGljeRIpLmJsb2NrbmluamEudjEuU2F2ZVJldGVudGlvblBvbGljeVJlcXVlc3QaKi5ibG9ja25pbmphLnYxLlNhdmVSZXRlbnRpb25Qb2xpY3lSZXNwb25zZRJ1ChZHZXRCYWNrdXBOb3RpZmljYXRpb25zEiwuYmxvY2tuaW5qYS52MS5HZXRCYWNrdXBOb3RpZmljYXRpb25zUmVxdWVzdBotLmJsb2NrbmluamEudjEuR2V0QmFja3VwTm90aWZpY2F0aW9uc1Jlc3BvbnNlEngKF1NhdmVCYWNrdXBOb3RpZmljYXRpb25zEi0uYmxvY2tuaW5qYS52MS5TYXZlQmFja3VwTm90aWZpY2F0aW9uc1JlcXVlc3QaLi5ibG9ja25pbmphLnYxLlNhdmVCYWNrdXBOb3RpZmljYXRpb25zUmVzcG9uc2USbwoUR2V0QmFja3VwRG93bmxvYWRVcmwSKi5ibG9ja25pbmphLnYxLkdldEJhY2t1cERvd25sb2FkVXJsUmVxdWVzdBorLmJsb2NrbmluamEudjEuR2V0QmFja3VwRG93bmxvYWRVcmxSZXNwb25zZRJsChNSdW5SZXRlbnRpb25DbGVhbnVwEikuYmxvY2tuaW5qYS52MS5SdW5SZXRlbnRpb25DbGVhbnVwUmVxdWVzdBoqLmJsb2NrbmluamEudjEuUnVuUmV0ZW50aW9uQ2xlYW51cFJlc3BvbnNlQsEBChFjb20uYmxvY2tuaW5qYS52MUILQmFja3VwUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_google_protobuf_timestamp], +); +/** + * Schedule type for automated backups + * + * @generated from enum blockninja.v1.ScheduleType + */ +var ScheduleType; +(function (ScheduleType) { + /** + * @generated from enum value: SCHEDULE_TYPE_UNSPECIFIED = 0; + */ + ScheduleType[(ScheduleType["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * @generated from enum value: SCHEDULE_TYPE_HOURLY = 1; + */ + ScheduleType[(ScheduleType["HOURLY"] = 1)] = "HOURLY"; + /** + * @generated from enum value: SCHEDULE_TYPE_DAILY = 2; + */ + ScheduleType[(ScheduleType["DAILY"] = 2)] = "DAILY"; + /** + * @generated from enum value: SCHEDULE_TYPE_WEEKLY = 3; + */ + ScheduleType[(ScheduleType["WEEKLY"] = 3)] = "WEEKLY"; + /** + * @generated from enum value: SCHEDULE_TYPE_MONTHLY = 4; + */ + ScheduleType[(ScheduleType["MONTHLY"] = 4)] = "MONTHLY"; +})(ScheduleType || (ScheduleType = {})); +/** + * Where to store backups + * + * @generated from enum blockninja.v1.StorageDestination + */ +var StorageDestination; +(function (StorageDestination) { + /** + * @generated from enum value: STORAGE_DESTINATION_UNSPECIFIED = 0; + */ + StorageDestination[(StorageDestination["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * @generated from enum value: STORAGE_DESTINATION_LOCAL = 1; + */ + StorageDestination[(StorageDestination["LOCAL"] = 1)] = "LOCAL"; + /** + * @generated from enum value: STORAGE_DESTINATION_S3 = 2; + */ + StorageDestination[(StorageDestination["S3"] = 2)] = "S3"; + /** + * @generated from enum value: STORAGE_DESTINATION_BOTH = 3; + */ + StorageDestination[(StorageDestination["BOTH"] = 3)] = "BOTH"; +})(StorageDestination || (StorageDestination = {})); +/** + * BackupService handles full site backup and restore using pg_dump/pg_restore + * + * @generated from service blockninja.v1.BackupService + */ +const BackupService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_backup, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/backup.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Start an async backup (returns immediately, notifies via realtime when done) + * + * @generated from rpc blockninja.v1.BackupService.StartBackup + */ +BackupService.method.startBackup; +/** + * List all available backups + * + * @generated from rpc blockninja.v1.BackupService.ListBackups + */ +BackupService.method.listBackups; +/** + * Delete a backup + * + * @generated from rpc blockninja.v1.BackupService.DeleteBackup + */ +BackupService.method.deleteBackup; +/** + * Download a completed backup + * + * @generated from rpc blockninja.v1.BackupService.DownloadBackup + */ +BackupService.method.downloadBackup; +/** + * Preview what a backup contains before restoring + * + * @generated from rpc blockninja.v1.BackupService.PreviewBackup + */ +BackupService.method.previewBackup; +/** + * Restore from a backup (destructive - replaces entire database) + * + * @generated from rpc blockninja.v1.BackupService.RestoreBackup + */ +BackupService.method.restoreBackup; +/** + * Get backup schedule configuration + * + * @generated from rpc blockninja.v1.BackupService.GetBackupSchedule + */ +BackupService.method.getBackupSchedule; +/** + * Save backup schedule configuration + * + * @generated from rpc blockninja.v1.BackupService.SaveBackupSchedule + */ +BackupService.method.saveBackupSchedule; +/** + * Get retention policy + * + * @generated from rpc blockninja.v1.BackupService.GetRetentionPolicy + */ +BackupService.method.getRetentionPolicy; +/** + * Save retention policy + * + * @generated from rpc blockninja.v1.BackupService.SaveRetentionPolicy + */ +BackupService.method.saveRetentionPolicy; +/** + * Get notification settings + * + * @generated from rpc blockninja.v1.BackupService.GetBackupNotifications + */ +BackupService.method.getBackupNotifications; +/** + * Save notification settings + * + * @generated from rpc blockninja.v1.BackupService.SaveBackupNotifications + */ +BackupService.method.saveBackupNotifications; +/** + * Get pre-signed download URL for S3 backup + * + * @generated from rpc blockninja.v1.BackupService.GetBackupDownloadUrl + */ +BackupService.method.getBackupDownloadUrl; +/** + * Run retention cleanup manually + * + * @generated from rpc blockninja.v1.BackupService.RunRetentionCleanup + */ +BackupService.method.runRetentionCleanup; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/billing.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/billing.proto. + */ +const file_blockninja_v1_billing = /*@__PURE__*/ fileDesc( + "ChtibG9ja25pbmphL3YxL2JpbGxpbmcucHJvdG8SDWJsb2NrbmluamEudjEipAEKEFN0cmlwZUNvbm5lY3Rpb24SCgoCaWQYASABKAkSGQoRc3RyaXBlX2FjY291bnRfaWQYAiABKAkSEAoIbGl2ZW1vZGUYAyABKAgSFQoNYnVzaW5lc3NfbmFtZRgEIAEoCRIOCgZzdGF0dXMYBSABKAkSMAoMY29ubmVjdGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCIcChpHZXRTdHJpcGVDb25uZWN0aW9uUmVxdWVzdCJqChtHZXRTdHJpcGVDb25uZWN0aW9uUmVzcG9uc2USMwoKY29ubmVjdGlvbhgBIAEoCzIfLmJsb2NrbmluamEudjEuU3RyaXBlQ29ubmVjdGlvbhIWCg5oYXNfY29ubmVjdGlvbhgCIAEoCCIwChpHZXRTdHJpcGVDb25uZWN0VVJMUmVxdWVzdBISCgpyZXR1cm5fdXJsGAEgASgJIjkKG0dldFN0cmlwZUNvbm5lY3RVUkxSZXNwb25zZRILCgN1cmwYASABKAkSDQoFc3RhdGUYAiABKAkiQQoUQ29ubmVjdFN0cmlwZVJlcXVlc3QSGgoSYXV0aG9yaXphdGlvbl9jb2RlGAEgASgJEg0KBXN0YXRlGAIgASgJIkwKFUNvbm5lY3RTdHJpcGVSZXNwb25zZRIzCgpjb25uZWN0aW9uGAEgASgLMh8uYmxvY2tuaW5qYS52MS5TdHJpcGVDb25uZWN0aW9uIhkKF0Rpc2Nvbm5lY3RTdHJpcGVSZXF1ZXN0IhoKGERpc2Nvbm5lY3RTdHJpcGVSZXNwb25zZSLSAQoKU3RyaXBlUGxhbhIKCgJpZBgBIAEoCRIPCgd0aWVyX2lkGAIgASgJEhEKCXRpZXJfbmFtZRgDIAEoCRIRCgl0aWVyX3NsdWcYBCABKAkSGQoRc3RyaXBlX3Byb2R1Y3RfaWQYBSABKAkSFwoPc3RyaXBlX3ByaWNlX2lkGAYgASgJEhgKEGJpbGxpbmdfaW50ZXJ2YWwYByABKAkSDgoGYW1vdW50GAggASgFEhAKCGN1cnJlbmN5GAkgASgJEhEKCWlzX2FjdGl2ZRgKIAEoCCIYChZMaXN0U3RyaXBlUGxhbnNSZXF1ZXN0IkMKF0xpc3RTdHJpcGVQbGFuc1Jlc3BvbnNlEigKBXBsYW5zGAEgAygLMhkuYmxvY2tuaW5qYS52MS5TdHJpcGVQbGFuIn8KF0NyZWF0ZVN0cmlwZVBsYW5SZXF1ZXN0Eg8KB3RpZXJfaWQYASABKAkSFwoPc3RyaXBlX3ByaWNlX2lkGAIgASgJEhgKEGJpbGxpbmdfaW50ZXJ2YWwYAyABKAkSDgoGYW1vdW50GAQgASgFEhAKCGN1cnJlbmN5GAUgASgJIkMKGENyZWF0ZVN0cmlwZVBsYW5SZXNwb25zZRInCgRwbGFuGAEgASgLMhkuYmxvY2tuaW5qYS52MS5TdHJpcGVQbGFuIo0BChdVcGRhdGVTdHJpcGVQbGFuUmVxdWVzdBIKCgJpZBgBIAEoCRIXCg9zdHJpcGVfcHJpY2VfaWQYAiABKAkSGAoQYmlsbGluZ19pbnRlcnZhbBgDIAEoCRIOCgZhbW91bnQYBCABKAUSEAoIY3VycmVuY3kYBSABKAkSEQoJaXNfYWN0aXZlGAYgASgIIkMKGFVwZGF0ZVN0cmlwZVBsYW5SZXNwb25zZRInCgRwbGFuGAEgASgLMhkuYmxvY2tuaW5qYS52MS5TdHJpcGVQbGFuIiUKF0RlbGV0ZVN0cmlwZVBsYW5SZXF1ZXN0EgoKAmlkGAEgASgJIhoKGERlbGV0ZVN0cmlwZVBsYW5SZXNwb25zZSIZChdTeW5jU3RyaXBlUHJpY2VzUmVxdWVzdCIwChhTeW5jU3RyaXBlUHJpY2VzUmVzcG9uc2USFAoMc3luY2VkX2NvdW50GAEgASgFIlgKHENyZWF0ZUNoZWNrb3V0U2Vzc2lvblJlcXVlc3QSDwoHcGxhbl9pZBgBIAEoCRITCgtzdWNjZXNzX3VybBgCIAEoCRISCgpjYW5jZWxfdXJsGAMgASgJIkkKHUNyZWF0ZUNoZWNrb3V0U2Vzc2lvblJlc3BvbnNlEhQKDGNoZWNrb3V0X3VybBgBIAEoCRISCgpzZXNzaW9uX2lkGAIgASgJIkEKFlVwZGF0ZVRlYW1TZWF0c1JlcXVlc3QSDwoHdGVhbV9pZBgBIAEoCRIWCg5uZXdfc2VhdF9jb3VudBgCIAEoBSI/ChdVcGRhdGVUZWFtU2VhdHNSZXNwb25zZRISCgpzZWF0X2NvdW50GAEgASgFEhAKCHByb3JhdGVkGAIgASgIIrcBCgtTdHJpcGVFdmVudBIKCgJpZBgBIAEoCRIXCg9zdHJpcGVfZXZlbnRfaWQYAiABKAkSEgoKZXZlbnRfdHlwZRgDIAEoCRIwCgxwcm9jZXNzZWRfYXQYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg0KBWVycm9yGAUgASgJEi4KCmNyZWF0ZWRfYXQYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIjoKF0xpc3RTdHJpcGVFdmVudHNSZXF1ZXN0EhEKCXBhZ2Vfc2l6ZRgBIAEoBRIMCgRwYWdlGAIgASgFIlsKGExpc3RTdHJpcGVFdmVudHNSZXNwb25zZRIqCgZldmVudHMYASADKAsyGi5ibG9ja25pbmphLnYxLlN0cmlwZUV2ZW50EhMKC3RvdGFsX2NvdW50GAIgASgFIhgKFkdldEJpbGxpbmdTdGF0c1JlcXVlc3QijQEKF0dldEJpbGxpbmdTdGF0c1Jlc3BvbnNlEhgKEHN0cmlwZV9jb25uZWN0ZWQYASABKAgSEAoIbGl2ZW1vZGUYAiABKAgSFAoMYWN0aXZlX3BsYW5zGAMgASgFEhkKEXRvdGFsX3N1YnNjcmliZXJzGAQgASgFEhUKDXJlY2VudF9ldmVudHMYBSABKAUywAoKDkJpbGxpbmdTZXJ2aWNlEmwKE0dldFN0cmlwZUNvbm5lY3Rpb24SKS5ibG9ja25pbmphLnYxLkdldFN0cmlwZUNvbm5lY3Rpb25SZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5HZXRTdHJpcGVDb25uZWN0aW9uUmVzcG9uc2USWgoNQ29ubmVjdFN0cmlwZRIjLmJsb2NrbmluamEudjEuQ29ubmVjdFN0cmlwZVJlcXVlc3QaJC5ibG9ja25pbmphLnYxLkNvbm5lY3RTdHJpcGVSZXNwb25zZRJjChBEaXNjb25uZWN0U3RyaXBlEiYuYmxvY2tuaW5qYS52MS5EaXNjb25uZWN0U3RyaXBlUmVxdWVzdBonLmJsb2NrbmluamEudjEuRGlzY29ubmVjdFN0cmlwZVJlc3BvbnNlEmwKE0dldFN0cmlwZUNvbm5lY3RVUkwSKS5ibG9ja25pbmphLnYxLkdldFN0cmlwZUNvbm5lY3RVUkxSZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5HZXRTdHJpcGVDb25uZWN0VVJMUmVzcG9uc2USYAoPTGlzdFN0cmlwZVBsYW5zEiUuYmxvY2tuaW5qYS52MS5MaXN0U3RyaXBlUGxhbnNSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5MaXN0U3RyaXBlUGxhbnNSZXNwb25zZRJjChBDcmVhdGVTdHJpcGVQbGFuEiYuYmxvY2tuaW5qYS52MS5DcmVhdGVTdHJpcGVQbGFuUmVxdWVzdBonLmJsb2NrbmluamEudjEuQ3JlYXRlU3RyaXBlUGxhblJlc3BvbnNlEmMKEFVwZGF0ZVN0cmlwZVBsYW4SJi5ibG9ja25pbmphLnYxLlVwZGF0ZVN0cmlwZVBsYW5SZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5VcGRhdGVTdHJpcGVQbGFuUmVzcG9uc2USYwoQRGVsZXRlU3RyaXBlUGxhbhImLmJsb2NrbmluamEudjEuRGVsZXRlU3RyaXBlUGxhblJlcXVlc3QaJy5ibG9ja25pbmphLnYxLkRlbGV0ZVN0cmlwZVBsYW5SZXNwb25zZRJjChBTeW5jU3RyaXBlUHJpY2VzEiYuYmxvY2tuaW5qYS52MS5TeW5jU3RyaXBlUHJpY2VzUmVxdWVzdBonLmJsb2NrbmluamEudjEuU3luY1N0cmlwZVByaWNlc1Jlc3BvbnNlEnIKFUNyZWF0ZUNoZWNrb3V0U2Vzc2lvbhIrLmJsb2NrbmluamEudjEuQ3JlYXRlQ2hlY2tvdXRTZXNzaW9uUmVxdWVzdBosLmJsb2NrbmluamEudjEuQ3JlYXRlQ2hlY2tvdXRTZXNzaW9uUmVzcG9uc2USYAoPVXBkYXRlVGVhbVNlYXRzEiUuYmxvY2tuaW5qYS52MS5VcGRhdGVUZWFtU2VhdHNSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5VcGRhdGVUZWFtU2VhdHNSZXNwb25zZRJjChBMaXN0U3RyaXBlRXZlbnRzEiYuYmxvY2tuaW5qYS52MS5MaXN0U3RyaXBlRXZlbnRzUmVxdWVzdBonLmJsb2NrbmluamEudjEuTGlzdFN0cmlwZUV2ZW50c1Jlc3BvbnNlEmAKD0dldEJpbGxpbmdTdGF0cxIlLmJsb2NrbmluamEudjEuR2V0QmlsbGluZ1N0YXRzUmVxdWVzdBomLmJsb2NrbmluamEudjEuR2V0QmlsbGluZ1N0YXRzUmVzcG9uc2VCwgEKEWNvbS5ibG9ja25pbmphLnYxQgxCaWxsaW5nUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_google_protobuf_timestamp], +); +/** + * @generated from service blockninja.v1.BillingService + */ +const BillingService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_billing, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/billing.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Stripe Connect + * + * @generated from rpc blockninja.v1.BillingService.GetStripeConnection + */ +BillingService.method.getStripeConnection; +/** + * @generated from rpc blockninja.v1.BillingService.ConnectStripe + */ +BillingService.method.connectStripe; +/** + * @generated from rpc blockninja.v1.BillingService.DisconnectStripe + */ +BillingService.method.disconnectStripe; +/** + * @generated from rpc blockninja.v1.BillingService.GetStripeConnectURL + */ +BillingService.method.getStripeConnectURL; +/** + * Plan mapping + * + * @generated from rpc blockninja.v1.BillingService.ListStripePlans + */ +BillingService.method.listStripePlans; +/** + * @generated from rpc blockninja.v1.BillingService.CreateStripePlan + */ +BillingService.method.createStripePlan; +/** + * @generated from rpc blockninja.v1.BillingService.UpdateStripePlan + */ +BillingService.method.updateStripePlan; +/** + * @generated from rpc blockninja.v1.BillingService.DeleteStripePlan + */ +BillingService.method.deleteStripePlan; +/** + * @generated from rpc blockninja.v1.BillingService.SyncStripePrices + */ +BillingService.method.syncStripePrices; +/** + * Checkout + * + * @generated from rpc blockninja.v1.BillingService.CreateCheckoutSession + */ +BillingService.method.createCheckoutSession; +/** + * Team billing (seat management) + * + * @generated from rpc blockninja.v1.BillingService.UpdateTeamSeats + */ +BillingService.method.updateTeamSeats; +/** + * Events + * + * @generated from rpc blockninja.v1.BillingService.ListStripeEvents + */ +BillingService.method.listStripeEvents; +/** + * Dashboard stats + * + * @generated from rpc blockninja.v1.BillingService.GetBillingStats + */ +BillingService.method.getBillingStats; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/blocks.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/blocks.proto. + */ +const file_blockninja_v1_blocks = /*@__PURE__*/ fileDesc( + "ChpibG9ja25pbmphL3YxL2Jsb2Nrcy5wcm90bxINYmxvY2tuaW5qYS52MSKgAwoFQmxvY2sSCgoCaWQYASABKAkSEQoJYmxvY2tfa2V5GAIgASgJEg0KBXRpdGxlGAMgASgJEigKB2NvbnRlbnQYBCABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0EhIKCnNvcnRfb3JkZXIYBiABKAUSDwoHdmVyc2lvbhgHIAEoBRIuCgpjcmVhdGVkX2F0GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAkgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIXCgpjcmVhdGVkX2J5GAogASgJSACIAQESFwoKdXBkYXRlZF9ieRgLIAEoCUgBiAEBEhkKDGh0bWxfY29udGVudBgMIAEoCUgCiAEBEhkKDGNvbnRlbnRfaGFzaBgNIAEoCUgDiAEBEgwKBHNsb3QYDiABKAlCDQoLX2NyZWF0ZWRfYnlCDQoLX3VwZGF0ZWRfYnlCDwoNX2h0bWxfY29udGVudEIPCg1fY29udGVudF9oYXNoSgQIBRAGIpcCCgxCbG9ja1ZlcnNpb24SCgoCaWQYASABKAkSEAoIYmxvY2tfaWQYAiABKAkSDwoHdmVyc2lvbhgDIAEoBRIRCglibG9ja19rZXkYBCABKAkSDQoFdGl0bGUYBSABKAkSKAoHY29udGVudBgGIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSEgoKc29ydF9vcmRlchgIIAEoBRIuCgpjcmVhdGVkX2F0GAkgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIXCgpjcmVhdGVkX2J5GAogASgJSACIAQESEQoEbmFtZRgLIAEoCUgBiAEBQg0KC19jcmVhdGVkX2J5QgcKBV9uYW1lSgQIBxAIIrcBCglCbG9ja1R5cGUSCwoDa2V5GAEgASgJEg0KBXRpdGxlGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEhMKC3NjaGVtYV9qc29uGAUgASgJEg4KBnNvdXJjZRgGIAEoCRIOCgZoaWRkZW4YByABKAgSGQoRaGFzX2ludGVybmFsX3Nsb3QYCCABKAgSEQoJZWRpdG9yX2pzGAkgASgJEhAKCGNhdGVnb3J5GAogASgJSgQIBBAFIr4BChNQYWdlQmxvY2tBc3NpZ25tZW50EgoKAmlkGAEgASgJEg8KB3BhZ2VfaWQYAiABKAkSEAoIYmxvY2tfaWQYAyABKAkSDAoEc2xvdBgEIAEoCRISCgpzb3J0X29yZGVyGAUgASgFEi4KCmNyZWF0ZWRfYXQYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhcKCmNyZWF0ZWRfYnkYByABKAlIAIgBAUINCgtfY3JlYXRlZF9ieSKWAQoRTGlzdEJsb2Nrc1JlcXVlc3QSLQoKcGFnaW5hdGlvbhgBIAEoCzIZLmJsb2NrbmluamEudjEuUGFnaW5hdGlvbhIdChBibG9ja19rZXlfZmlsdGVyGAIgASgJSACIAQESEwoGc2VhcmNoGAMgASgJSAGIAQFCEwoRX2Jsb2NrX2tleV9maWx0ZXJCCQoHX3NlYXJjaCJxChJMaXN0QmxvY2tzUmVzcG9uc2USJAoGYmxvY2tzGAEgAygLMhQuYmxvY2tuaW5qYS52MS5CbG9jaxI1CgpwYWdpbmF0aW9uGAIgASgLMiEuYmxvY2tuaW5qYS52MS5QYWdpbmF0aW9uUmVzcG9uc2UiIwoPR2V0QmxvY2tSZXF1ZXN0EgoKAmlkGAEgASgJSgQIAhADIj0KEEdldEJsb2NrUmVzcG9uc2USIwoFYmxvY2sYASABKAsyFC5ibG9ja25pbmphLnYxLkJsb2NrSgQIAhADItQCChJDcmVhdGVCbG9ja1JlcXVlc3QSEQoJYmxvY2tfa2V5GAEgASgJEg0KBXRpdGxlGAIgASgJEigKB2NvbnRlbnQYAyABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0EhQKB3BhZ2VfaWQYBiABKAlIAIgBARIRCgRzbG90GAcgASgJSAGIAQESGQoMaHRtbF9jb250ZW50GAggASgJSAKIAQESIgoVaW5zZXJ0X2FmdGVyX2Jsb2NrX2lkGAkgASgJSAOIAQESIwoWaW5zZXJ0X2JlZm9yZV9ibG9ja19pZBgKIAEoCUgEiAEBQgoKCF9wYWdlX2lkQgcKBV9zbG90Qg8KDV9odG1sX2NvbnRlbnRCGAoWX2luc2VydF9hZnRlcl9ibG9ja19pZEIZChdfaW5zZXJ0X2JlZm9yZV9ibG9ja19pZEoECAQQBUoECAUQBiI6ChNDcmVhdGVCbG9ja1Jlc3BvbnNlEiMKBWJsb2NrGAEgASgLMhQuYmxvY2tuaW5qYS52MS5CbG9jayKFAgoSVXBkYXRlQmxvY2tSZXF1ZXN0EgoKAmlkGAEgASgJEhYKCWJsb2NrX2tleRgCIAEoCUgAiAEBEhIKBXRpdGxlGAMgASgJSAGIAQESLQoHY29udGVudBgEIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3RIAogBARIZCgxodG1sX2NvbnRlbnQYByABKAlIA4gBARIaCg1leHBlY3RlZF9oYXNoGAggASgJSASIAQFCDAoKX2Jsb2NrX2tleUIICgZfdGl0bGVCCgoIX2NvbnRlbnRCDwoNX2h0bWxfY29udGVudEIQCg5fZXhwZWN0ZWRfaGFzaEoECAUQBkoECAYQByJLChNVcGRhdGVCbG9ja1Jlc3BvbnNlEiMKBWJsb2NrGAEgASgLMhQuYmxvY2tuaW5qYS52MS5CbG9jaxIPCgdza2lwcGVkGAIgASgIIl4KEkRlbGV0ZUJsb2NrUmVxdWVzdBIKCgJpZBgBIAEoCRIUCgdwYWdlX2lkGAIgASgJSACIAQESEQoEc2xvdBgDIAEoCUgBiAEBQgoKCF9wYWdlX2lkQgcKBV9zbG90IhUKE0RlbGV0ZUJsb2NrUmVzcG9uc2UiWwoYTGlzdEJsb2NrVmVyc2lvbnNSZXF1ZXN0EhAKCGJsb2NrX2lkGAEgASgJEi0KCnBhZ2luYXRpb24YAiABKAsyGS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb24igQEKGUxpc3RCbG9ja1ZlcnNpb25zUmVzcG9uc2USLQoIdmVyc2lvbnMYASADKAsyGy5ibG9ja25pbmphLnYxLkJsb2NrVmVyc2lvbhI1CgpwYWdpbmF0aW9uGAIgASgLMiEuYmxvY2tuaW5qYS52MS5QYWdpbmF0aW9uUmVzcG9uc2UiNwoSUmV2ZXJ0QmxvY2tSZXF1ZXN0EhAKCGJsb2NrX2lkGAEgASgJEg8KB3ZlcnNpb24YAiABKAUiOgoTUmV2ZXJ0QmxvY2tSZXNwb25zZRIjCgVibG9jaxgBIAEoCzIULmJsb2NrbmluamEudjEuQmxvY2siUAodVXBkYXRlQmxvY2tWZXJzaW9uTmFtZVJlcXVlc3QSEAoIYmxvY2tfaWQYASABKAkSDwoHdmVyc2lvbhgCIAEoBRIMCgRuYW1lGAMgASgJIk4KHlVwZGF0ZUJsb2NrVmVyc2lvbk5hbWVSZXNwb25zZRIsCgd2ZXJzaW9uGAEgASgLMhsuYmxvY2tuaW5qYS52MS5CbG9ja1ZlcnNpb24iQwoUR2V0UGFnZUJsb2Nrc1JlcXVlc3QSDwoHcGFnZV9pZBgBIAEoCRIRCgRzbG90GAIgASgJSACIAQFCBwoFX3Nsb3QiiwMKFUdldFBhZ2VCbG9ja3NSZXNwb25zZRI+CgVzbG90cxgBIAMoCzIvLmJsb2NrbmluamEudjEuR2V0UGFnZUJsb2Nrc1Jlc3BvbnNlLlNsb3RzRW50cnkSGwoObWFzdGVyX3BhZ2VfaWQYAiABKAlIAIgBARJLCgxtYXN0ZXJfc2xvdHMYAyADKAsyNS5ibG9ja25pbmphLnYxLkdldFBhZ2VCbG9ja3NSZXNwb25zZS5NYXN0ZXJTbG90c0VudHJ5EhcKD2F2YWlsYWJsZV9zbG90cxgEIAMoCRpKCgpTbG90c0VudHJ5EgsKA2tleRgBIAEoCRIrCgV2YWx1ZRgCIAEoCzIcLmJsb2NrbmluamEudjEuUGFnZUJsb2NrU2xvdDoCOAEaUAoQTWFzdGVyU2xvdHNFbnRyeRILCgNrZXkYASABKAkSKwoFdmFsdWUYAiABKAsyHC5ibG9ja25pbmphLnYxLlBhZ2VCbG9ja1Nsb3Q6AjgBQhEKD19tYXN0ZXJfcGFnZV9pZCI1Cg1QYWdlQmxvY2tTbG90EiQKBmJsb2NrcxgBIAMoCzIULmJsb2NrbmluamEudjEuQmxvY2siXwoYQXNzaWduQmxvY2tUb1BhZ2VSZXF1ZXN0Eg8KB3BhZ2VfaWQYASABKAkSEAoIYmxvY2tfaWQYAiABKAkSDAoEc2xvdBgDIAEoCRISCgpzb3J0X29yZGVyGAQgASgFIlMKGUFzc2lnbkJsb2NrVG9QYWdlUmVzcG9uc2USNgoKYXNzaWdubWVudBgBIAEoCzIiLmJsb2NrbmluamEudjEuUGFnZUJsb2NrQXNzaWdubWVudCJNChpSZW1vdmVCbG9ja0Zyb21QYWdlUmVxdWVzdBIPCgdwYWdlX2lkGAEgASgJEhAKCGJsb2NrX2lkGAIgASgJEgwKBHNsb3QYAyABKAkiHQobUmVtb3ZlQmxvY2tGcm9tUGFnZVJlc3BvbnNlIp0CChtVcGRhdGVQYWdlQmxvY2tPcmRlclJlcXVlc3QSDwoHcGFnZV9pZBgBIAEoCRIQCghibG9ja19pZBgCIAEoCRIMCgRzbG90GAMgASgJEiIKFWluc2VydF9hZnRlcl9ibG9ja19pZBgHIAEoCUgAiAEBEiMKFmluc2VydF9iZWZvcmVfYmxvY2tfaWQYCCABKAlIAYgBAUIYChZfaW5zZXJ0X2FmdGVyX2Jsb2NrX2lkQhkKF19pbnNlcnRfYmVmb3JlX2Jsb2NrX2lkSgQIBBAFSgQIBRAGSgQIBhAHSgQICRAKUgpzb3J0X29yZGVyUglwYXJlbnRfaWRSDnRhcmdldF9wYWdlX2lkUhB0YXJnZXRfcGFyZW50X2lkIj8KHFVwZGF0ZVBhZ2VCbG9ja09yZGVyUmVzcG9uc2USHwoXbW92ZWRfdG9fZGlmZmVyZW50X3BhZ2UYASABKAgiFwoVTGlzdEJsb2NrVHlwZXNSZXF1ZXN0IkcKFkxpc3RCbG9ja1R5cGVzUmVzcG9uc2USLQoLYmxvY2tfdHlwZXMYASADKAsyGC5ibG9ja25pbmphLnYxLkJsb2NrVHlwZSItChlHZXRQYWdlc1VzaW5nQmxvY2tSZXF1ZXN0EhAKCGJsb2NrX2lkGAEgASgJIkUKGkdldFBhZ2VzVXNpbmdCbG9ja1Jlc3BvbnNlEicKBXBhZ2VzGAEgAygLMhguYmxvY2tuaW5qYS52MS5QYWdlVXNhZ2UiUQoJUGFnZVVzYWdlEg8KB3BhZ2VfaWQYASABKAkSEgoKcGFnZV90aXRsZRgCIAEoCRIRCglwYWdlX3NsdWcYAyABKAkSDAoEc2xvdBgEIAEoCSKnAQoNQmxvY2tUZW1wbGF0ZRIKCgJpZBgBIAEoCRIRCglibG9ja19rZXkYAiABKAkSFAoMdGVtcGxhdGVfa2V5GAMgASgJEgwKBG5hbWUYBCABKAkSEwoLZGVzY3JpcHRpb24YBSABKAkSDgoGc291cmNlGAYgASgJEi4KCmNyZWF0ZWRfYXQYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIkEKGUxpc3RCbG9ja1RlbXBsYXRlc1JlcXVlc3QSFgoJYmxvY2tfa2V5GAEgASgJSACIAQFCDAoKX2Jsb2NrX2tleSJNChpMaXN0QmxvY2tUZW1wbGF0ZXNSZXNwb25zZRIvCgl0ZW1wbGF0ZXMYASADKAsyHC5ibG9ja25pbmphLnYxLkJsb2NrVGVtcGxhdGUiLQoZR2VuZXJhdGVCbG9ja1RpdGxlUmVxdWVzdBIQCghibG9ja19pZBgBIAEoCSJAChpHZW5lcmF0ZUJsb2NrVGl0bGVSZXNwb25zZRINCgV0aXRsZRgBIAEoCRITCgtleHBsYW5hdGlvbhgCIAEoCSJsChpSZW5hbWVCbG9ja1ZhcmlhYmxlUmVxdWVzdBIQCghibG9ja19pZBgBIAEoCRIQCghvbGRfbmFtZRgCIAEoCRIQCghuZXdfbmFtZRgDIAEoCRIYChBtaWdyYXRlX3ZlcnNpb25zGAQgASgIIm4KG1JlbmFtZUJsb2NrVmFyaWFibGVSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEiMKG3RlbXBsYXRlX3JlZmVyZW5jZXNfdXBkYXRlZBgCIAEoBRIZChF2ZXJzaW9uc19taWdyYXRlZBgDIAEoBSJFCgtGaWVsZFJlbmFtZRIQCghvbGRfbmFtZRgBIAEoCRIQCghuZXdfbmFtZRgCIAEoCRISCgpjb25maWRlbmNlGAMgASgCIoYDChBCbG9ja0hpc3RvcnlJdGVtEgoKAmlkGAEgASgJEhEKCWJsb2NrX2tleRgCIAEoCRINCgV0aXRsZRgDIAEoCRIoCgdjb250ZW50GAQgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIqCgZzdGF0dXMYBSABKA4yGi5ibG9ja25pbmphLnYxLkJsb2NrU3RhdHVzEg8KB3ZlcnNpb24YBiABKAUSFQoNdmVyc2lvbl9jb3VudBgHIAEoBRISCgpwYWdlX2NvdW50GAggASgFEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhkKDGh0bWxfY29udGVudBgLIAEoCUgAiAEBEhcKCmNyZWF0ZWRfYnkYDCABKAlIAYgBAUIPCg1faHRtbF9jb250ZW50Qg0KC19jcmVhdGVkX2J5IoYDChdMaXN0QmxvY2tIaXN0b3J5UmVxdWVzdBItCgpwYWdpbmF0aW9uGAEgASgLMhkuYmxvY2tuaW5qYS52MS5QYWdpbmF0aW9uEh0KEGJsb2NrX2tleV9maWx0ZXIYAiABKAlIAIgBARIYCgtzZWFyY2hfdGVybRgDIAEoCUgBiAEBEjYKDXN0YXR1c19maWx0ZXIYBCABKA4yGi5ibG9ja25pbmphLnYxLkJsb2NrU3RhdHVzSAKIAQESNgoNY3JlYXRlZF9hZnRlchgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIA4gBARI3Cg5jcmVhdGVkX2JlZm9yZRgGIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIBIgBAUITChFfYmxvY2tfa2V5X2ZpbHRlckIOCgxfc2VhcmNoX3Rlcm1CEAoOX3N0YXR1c19maWx0ZXJCEAoOX2NyZWF0ZWRfYWZ0ZXJCEQoPX2NyZWF0ZWRfYmVmb3JlIrMBChhMaXN0QmxvY2tIaXN0b3J5UmVzcG9uc2USLwoGYmxvY2tzGAEgAygLMh8uYmxvY2tuaW5qYS52MS5CbG9ja0hpc3RvcnlJdGVtEjUKCnBhZ2luYXRpb24YAiABKAsyIS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb25SZXNwb25zZRIvCgVzdGF0cxgDIAEoCzIgLmJsb2NrbmluamEudjEuQmxvY2tIaXN0b3J5U3RhdHMiYQoeR2V0RGVsZXRlZEJsb2NrVmVyc2lvbnNSZXF1ZXN0EhAKCGJsb2NrX2lkGAEgASgJEi0KCnBhZ2luYXRpb24YAiABKAsyGS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb24ihwEKH0dldERlbGV0ZWRCbG9ja1ZlcnNpb25zUmVzcG9uc2USLQoIdmVyc2lvbnMYASADKAsyGy5ibG9ja25pbmphLnYxLkJsb2NrVmVyc2lvbhI1CgpwYWdpbmF0aW9uGAIgASgLMiEuYmxvY2tuaW5qYS52MS5QYWdpbmF0aW9uUmVzcG9uc2UifQoaUmVjb3ZlckRlbGV0ZWRCbG9ja1JlcXVlc3QSEAoIYmxvY2tfaWQYASABKAkSDwoHdmVyc2lvbhgCIAEoBRIUCgdwYWdlX2lkGAMgASgJSACIAQESEQoEc2xvdBgEIAEoCUgBiAEBQgoKCF9wYWdlX2lkQgcKBV9zbG90IkIKG1JlY292ZXJEZWxldGVkQmxvY2tSZXNwb25zZRIjCgVibG9jaxgBIAEoCzIULmJsb2NrbmluamEudjEuQmxvY2siJQoRUHVyZ2VCbG9ja1JlcXVlc3QSEAoIYmxvY2tfaWQYASABKAkiLgoSUHVyZ2VCbG9ja1Jlc3BvbnNlEhgKEHZlcnNpb25zX2RlbGV0ZWQYASABKAUiHQobR2V0QmxvY2tIaXN0b3J5U3RhdHNSZXF1ZXN0IokBChFCbG9ja0hpc3RvcnlTdGF0cxIUCgx0b3RhbF9ibG9ja3MYASABKAUSFQoNYWN0aXZlX2Jsb2NrcxgCIAEoBRIXCg9vcnBoYW5lZF9ibG9ja3MYAyABKAUSFgoOZGVsZXRlZF9ibG9ja3MYBCABKAUSFgoOdG90YWxfdmVyc2lvbnMYBSABKAUiTwocR2V0QmxvY2tIaXN0b3J5U3RhdHNSZXNwb25zZRIvCgVzdGF0cxgBIAEoCzIgLmJsb2NrbmluamEudjEuQmxvY2tIaXN0b3J5U3RhdHMiMgoeR2V0QmxvY2tIaXN0b3JpY2FsUGFnZXNSZXF1ZXN0EhAKCGJsb2NrX2lkGAEgASgJIssBChNIaXN0b3JpY2FsUGFnZVVzYWdlEg8KB3BhZ2VfaWQYASABKAkSEgoKcGFnZV90aXRsZRgCIAEoCRIRCglwYWdlX3NsdWcYAyABKAkSDgoGc3RhdHVzGAQgASgJEhQKDHBhZ2VfdmVyc2lvbhgFIAEoBRIMCgRzbG90GAYgASgJEi8KC3NuYXBzaG90X2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIXCg9jdXJyZW50bHlfdXNpbmcYCCABKAgiVAofR2V0QmxvY2tIaXN0b3JpY2FsUGFnZXNSZXNwb25zZRIxCgVwYWdlcxgBIAMoCzIiLmJsb2NrbmluamEudjEuSGlzdG9yaWNhbFBhZ2VVc2FnZSp5CgtCbG9ja1N0YXR1cxIcChhCTE9DS19TVEFUVVNfVU5TUEVDSUZJRUQQABIXChNCTE9DS19TVEFUVVNfQUNUSVZFEAESGQoVQkxPQ0tfU1RBVFVTX09SUEhBTkVEEAISGAoUQkxPQ0tfU1RBVFVTX0RFTEVURUQQAzKiEgoNQmxvY2tzU2VydmljZRJRCgpMaXN0QmxvY2tzEiAuYmxvY2tuaW5qYS52MS5MaXN0QmxvY2tzUmVxdWVzdBohLmJsb2NrbmluamEudjEuTGlzdEJsb2Nrc1Jlc3BvbnNlEksKCEdldEJsb2NrEh4uYmxvY2tuaW5qYS52MS5HZXRCbG9ja1JlcXVlc3QaHy5ibG9ja25pbmphLnYxLkdldEJsb2NrUmVzcG9uc2USVAoLQ3JlYXRlQmxvY2sSIS5ibG9ja25pbmphLnYxLkNyZWF0ZUJsb2NrUmVxdWVzdBoiLmJsb2NrbmluamEudjEuQ3JlYXRlQmxvY2tSZXNwb25zZRJUCgtVcGRhdGVCbG9jaxIhLmJsb2NrbmluamEudjEuVXBkYXRlQmxvY2tSZXF1ZXN0GiIuYmxvY2tuaW5qYS52MS5VcGRhdGVCbG9ja1Jlc3BvbnNlElQKC0RlbGV0ZUJsb2NrEiEuYmxvY2tuaW5qYS52MS5EZWxldGVCbG9ja1JlcXVlc3QaIi5ibG9ja25pbmphLnYxLkRlbGV0ZUJsb2NrUmVzcG9uc2USZgoRTGlzdEJsb2NrVmVyc2lvbnMSJy5ibG9ja25pbmphLnYxLkxpc3RCbG9ja1ZlcnNpb25zUmVxdWVzdBooLmJsb2NrbmluamEudjEuTGlzdEJsb2NrVmVyc2lvbnNSZXNwb25zZRJUCgtSZXZlcnRCbG9jaxIhLmJsb2NrbmluamEudjEuUmV2ZXJ0QmxvY2tSZXF1ZXN0GiIuYmxvY2tuaW5qYS52MS5SZXZlcnRCbG9ja1Jlc3BvbnNlEnUKFlVwZGF0ZUJsb2NrVmVyc2lvbk5hbWUSLC5ibG9ja25pbmphLnYxLlVwZGF0ZUJsb2NrVmVyc2lvbk5hbWVSZXF1ZXN0Gi0uYmxvY2tuaW5qYS52MS5VcGRhdGVCbG9ja1ZlcnNpb25OYW1lUmVzcG9uc2USWgoNR2V0UGFnZUJsb2NrcxIjLmJsb2NrbmluamEudjEuR2V0UGFnZUJsb2Nrc1JlcXVlc3QaJC5ibG9ja25pbmphLnYxLkdldFBhZ2VCbG9ja3NSZXNwb25zZRJmChFBc3NpZ25CbG9ja1RvUGFnZRInLmJsb2NrbmluamEudjEuQXNzaWduQmxvY2tUb1BhZ2VSZXF1ZXN0GiguYmxvY2tuaW5qYS52MS5Bc3NpZ25CbG9ja1RvUGFnZVJlc3BvbnNlEmwKE1JlbW92ZUJsb2NrRnJvbVBhZ2USKS5ibG9ja25pbmphLnYxLlJlbW92ZUJsb2NrRnJvbVBhZ2VSZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5SZW1vdmVCbG9ja0Zyb21QYWdlUmVzcG9uc2USbwoUVXBkYXRlUGFnZUJsb2NrT3JkZXISKi5ibG9ja25pbmphLnYxLlVwZGF0ZVBhZ2VCbG9ja09yZGVyUmVxdWVzdBorLmJsb2NrbmluamEudjEuVXBkYXRlUGFnZUJsb2NrT3JkZXJSZXNwb25zZRJdCg5MaXN0QmxvY2tUeXBlcxIkLmJsb2NrbmluamEudjEuTGlzdEJsb2NrVHlwZXNSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5MaXN0QmxvY2tUeXBlc1Jlc3BvbnNlEmkKEkxpc3RCbG9ja1RlbXBsYXRlcxIoLmJsb2NrbmluamEudjEuTGlzdEJsb2NrVGVtcGxhdGVzUmVxdWVzdBopLmJsb2NrbmluamEudjEuTGlzdEJsb2NrVGVtcGxhdGVzUmVzcG9uc2USaQoSR2V0UGFnZXNVc2luZ0Jsb2NrEiguYmxvY2tuaW5qYS52MS5HZXRQYWdlc1VzaW5nQmxvY2tSZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5HZXRQYWdlc1VzaW5nQmxvY2tSZXNwb25zZRJpChJHZW5lcmF0ZUJsb2NrVGl0bGUSKC5ibG9ja25pbmphLnYxLkdlbmVyYXRlQmxvY2tUaXRsZVJlcXVlc3QaKS5ibG9ja25pbmphLnYxLkdlbmVyYXRlQmxvY2tUaXRsZVJlc3BvbnNlEmwKE1JlbmFtZUJsb2NrVmFyaWFibGUSKS5ibG9ja25pbmphLnYxLlJlbmFtZUJsb2NrVmFyaWFibGVSZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5SZW5hbWVCbG9ja1ZhcmlhYmxlUmVzcG9uc2USYwoQTGlzdEJsb2NrSGlzdG9yeRImLmJsb2NrbmluamEudjEuTGlzdEJsb2NrSGlzdG9yeVJlcXVlc3QaJy5ibG9ja25pbmphLnYxLkxpc3RCbG9ja0hpc3RvcnlSZXNwb25zZRJ4ChdHZXREZWxldGVkQmxvY2tWZXJzaW9ucxItLmJsb2NrbmluamEudjEuR2V0RGVsZXRlZEJsb2NrVmVyc2lvbnNSZXF1ZXN0Gi4uYmxvY2tuaW5qYS52MS5HZXREZWxldGVkQmxvY2tWZXJzaW9uc1Jlc3BvbnNlEmwKE1JlY292ZXJEZWxldGVkQmxvY2sSKS5ibG9ja25pbmphLnYxLlJlY292ZXJEZWxldGVkQmxvY2tSZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5SZWNvdmVyRGVsZXRlZEJsb2NrUmVzcG9uc2USUQoKUHVyZ2VCbG9jaxIgLmJsb2NrbmluamEudjEuUHVyZ2VCbG9ja1JlcXVlc3QaIS5ibG9ja25pbmphLnYxLlB1cmdlQmxvY2tSZXNwb25zZRJvChRHZXRCbG9ja0hpc3RvcnlTdGF0cxIqLmJsb2NrbmluamEudjEuR2V0QmxvY2tIaXN0b3J5U3RhdHNSZXF1ZXN0GisuYmxvY2tuaW5qYS52MS5HZXRCbG9ja0hpc3RvcnlTdGF0c1Jlc3BvbnNlEngKF0dldEJsb2NrSGlzdG9yaWNhbFBhZ2VzEi0uYmxvY2tuaW5qYS52MS5HZXRCbG9ja0hpc3RvcmljYWxQYWdlc1JlcXVlc3QaLi5ibG9ja25pbmphLnYxLkdldEJsb2NrSGlzdG9yaWNhbFBhZ2VzUmVzcG9uc2VCwQEKEWNvbS5ibG9ja25pbmphLnYxQgtCbG9ja3NQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_blockninja_v1_common, file_google_protobuf_timestamp, file_google_protobuf_struct], +); +/** + * Block status for history view + * + * @generated from enum blockninja.v1.BlockStatus + */ +var BlockStatus; +(function (BlockStatus) { + /** + * @generated from enum value: BLOCK_STATUS_UNSPECIFIED = 0; + */ + BlockStatus[(BlockStatus["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * Block exists and is assigned to pages + * + * @generated from enum value: BLOCK_STATUS_ACTIVE = 1; + */ + BlockStatus[(BlockStatus["ACTIVE"] = 1)] = "ACTIVE"; + /** + * Block exists but not assigned to any page + * + * @generated from enum value: BLOCK_STATUS_ORPHANED = 2; + */ + BlockStatus[(BlockStatus["ORPHANED"] = 2)] = "ORPHANED"; + /** + * Block deleted, only versions remain + * + * @generated from enum value: BLOCK_STATUS_DELETED = 3; + */ + BlockStatus[(BlockStatus["DELETED"] = 3)] = "DELETED"; +})(BlockStatus || (BlockStatus = {})); +/** + * @generated from service blockninja.v1.BlocksService + */ +const BlocksService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_blocks, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/blocks.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Block CRUD + * + * @generated from rpc blockninja.v1.BlocksService.ListBlocks + */ +BlocksService.method.listBlocks; +/** + * @generated from rpc blockninja.v1.BlocksService.GetBlock + */ +BlocksService.method.getBlock; +/** + * @generated from rpc blockninja.v1.BlocksService.CreateBlock + */ +BlocksService.method.createBlock; +/** + * @generated from rpc blockninja.v1.BlocksService.UpdateBlock + */ +BlocksService.method.updateBlock; +/** + * @generated from rpc blockninja.v1.BlocksService.DeleteBlock + */ +BlocksService.method.deleteBlock; +/** + * Block versions + * + * @generated from rpc blockninja.v1.BlocksService.ListBlockVersions + */ +BlocksService.method.listBlockVersions; +/** + * @generated from rpc blockninja.v1.BlocksService.RevertBlock + */ +BlocksService.method.revertBlock; +/** + * @generated from rpc blockninja.v1.BlocksService.UpdateBlockVersionName + */ +BlocksService.method.updateBlockVersionName; +/** + * Page-Block assignments + * + * @generated from rpc blockninja.v1.BlocksService.GetPageBlocks + */ +BlocksService.method.getPageBlocks; +/** + * @generated from rpc blockninja.v1.BlocksService.AssignBlockToPage + */ +BlocksService.method.assignBlockToPage; +/** + * @generated from rpc blockninja.v1.BlocksService.RemoveBlockFromPage + */ +BlocksService.method.removeBlockFromPage; +/** + * @generated from rpc blockninja.v1.BlocksService.UpdatePageBlockOrder + */ +BlocksService.method.updatePageBlockOrder; +/** + * Block types + * + * @generated from rpc blockninja.v1.BlocksService.ListBlockTypes + */ +BlocksService.method.listBlockTypes; +/** + * Block templates (visual variants for blocks) + * + * @generated from rpc blockninja.v1.BlocksService.ListBlockTemplates + */ +BlocksService.method.listBlockTemplates; +/** + * Block usage + * + * @generated from rpc blockninja.v1.BlocksService.GetPagesUsingBlock + */ +BlocksService.method.getPagesUsingBlock; +/** + * AI-powered title generation + * + * @generated from rpc blockninja.v1.BlocksService.GenerateBlockTitle + */ +BlocksService.method.generateBlockTitle; +/** + * Variable rename for HTML/smart blocks + * + * @generated from rpc blockninja.v1.BlocksService.RenameBlockVariable + */ +BlocksService.method.renameBlockVariable; +/** + * Block History (admin feature for searching and recovering deleted/orphaned blocks) + * + * @generated from rpc blockninja.v1.BlocksService.ListBlockHistory + */ +BlocksService.method.listBlockHistory; +/** + * @generated from rpc blockninja.v1.BlocksService.GetDeletedBlockVersions + */ +BlocksService.method.getDeletedBlockVersions; +/** + * @generated from rpc blockninja.v1.BlocksService.RecoverDeletedBlock + */ +BlocksService.method.recoverDeletedBlock; +/** + * @generated from rpc blockninja.v1.BlocksService.PurgeBlock + */ +BlocksService.method.purgeBlock; +/** + * @generated from rpc blockninja.v1.BlocksService.GetBlockHistoryStats + */ +BlocksService.method.getBlockHistoryStats; +/** + * @generated from rpc blockninja.v1.BlocksService.GetBlockHistoricalPages + */ +BlocksService.method.getBlockHistoricalPages; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/posts.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/posts.proto. + */ +const file_blockninja_v1_posts = /*@__PURE__*/ fileDesc( + "ChlibG9ja25pbmphL3YxL3Bvc3RzLnByb3RvEg1ibG9ja25pbmphLnYxIsgDCgRQb3N0EgoKAmlkGAEgASgJEgwKBHNsdWcYAiABKAkSDQoFdGl0bGUYAyABKAkSFAoHZXhjZXJwdBgEIAEoCUgAiAEBEh4KEWZlYXR1cmVkX2ltYWdlX2lkGAUgASgJSAGIAQESFgoJYXV0aG9yX2lkGAYgASgJSAKIAQESGAoLYXV0aG9yX25hbWUYByABKAlIA4gBARIhChRyZWFkaW5nX3RpbWVfbWludXRlcxgIIAEoBUgEiAEBEhMKC2lzX2ZlYXR1cmVkGAkgASgIEjUKDHB1Ymxpc2hlZF9hdBgKIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIBYgBARIrCgpjYXRlZ29yaWVzGAsgAygLMhcuYmxvY2tuaW5qYS52MS5DYXRlZ29yeRIpCgZzdGF0dXMYDCABKA4yGS5ibG9ja25pbmphLnYxLlBhZ2VTdGF0dXNCCgoIX2V4Y2VycHRCFAoSX2ZlYXR1cmVkX2ltYWdlX2lkQgwKCl9hdXRob3JfaWRCDgoMX2F1dGhvcl9uYW1lQhcKFV9yZWFkaW5nX3RpbWVfbWludXRlc0IPCg1fcHVibGlzaGVkX2F0Io4BCghDYXRlZ29yeRIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEgwKBHNsdWcYAyABKAkSGAoLZGVzY3JpcHRpb24YBCABKAlIAIgBARISCgpwb3N0X2NvdW50GAUgASgDEhIKBWNvbG9yGAYgASgJSAGIAQFCDgoMX2Rlc2NyaXB0aW9uQggKBl9jb2xvciK7AQoQTGlzdFBvc3RzUmVxdWVzdBItCgpwYWdpbmF0aW9uGAEgASgLMhkuYmxvY2tuaW5qYS52MS5QYWdpbmF0aW9uEhgKC2NhdGVnb3J5X2lkGAIgASgJSACIAQESFgoJYXV0aG9yX2lkGAMgASgJSAGIAQESGAoLaXNfZmVhdHVyZWQYBCABKAhIAogBAUIOCgxfY2F0ZWdvcnlfaWRCDAoKX2F1dGhvcl9pZEIOCgxfaXNfZmVhdHVyZWQibgoRTGlzdFBvc3RzUmVzcG9uc2USIgoFcG9zdHMYASADKAsyEy5ibG9ja25pbmphLnYxLlBvc3QSNQoKcGFnaW5hdGlvbhgCIAEoCzIhLmJsb2NrbmluamEudjEuUGFnaW5hdGlvblJlc3BvbnNlIh4KDkdldFBvc3RSZXF1ZXN0EgwKBHNsdWcYASABKAkiXAoPR2V0UG9zdFJlc3BvbnNlEiEKBHBvc3QYASABKAsyEy5ibG9ja25pbmphLnYxLlBvc3QSJgoJZnVsbF9wYWdlGAIgASgLMhMuYmxvY2tuaW5qYS52MS5QYWdlIkQKFkxpc3RQb3N0QXJjaGl2ZVJlcXVlc3QSDAoEeWVhchgBIAEoBRISCgVtb250aBgCIAEoBUgAiAEBQggKBl9tb250aCI9ChdMaXN0UG9zdEFyY2hpdmVSZXNwb25zZRIiCgVwb3N0cxgBIAMoCzITLmJsb2NrbmluamEudjEuUG9zdCIZChdHZXRBcmNoaXZlQ291bnRzUmVxdWVzdCJHChhHZXRBcmNoaXZlQ291bnRzUmVzcG9uc2USKwoGY291bnRzGAEgAygLMhsuYmxvY2tuaW5qYS52MS5BcmNoaXZlQ291bnQiOgoMQXJjaGl2ZUNvdW50EgwKBHllYXIYASABKAUSDQoFbW9udGgYAiABKAUSDQoFY291bnQYAyABKAMiOAoWR2V0UmVsYXRlZFBvc3RzUmVxdWVzdBIPCgdwb3N0X2lkGAEgASgJEg0KBWxpbWl0GAIgASgFIj0KF0dldFJlbGF0ZWRQb3N0c1Jlc3BvbnNlEiIKBXBvc3RzGAEgAygLMhMuYmxvY2tuaW5qYS52MS5Qb3N0IlsKF0dldFBvc3RzQnlBdXRob3JSZXF1ZXN0EhEKCWF1dGhvcl9pZBgBIAEoCRItCgpwYWdpbmF0aW9uGAIgASgLMhkuYmxvY2tuaW5qYS52MS5QYWdpbmF0aW9uInUKGEdldFBvc3RzQnlBdXRob3JSZXNwb25zZRIiCgVwb3N0cxgBIAMoCzITLmJsb2NrbmluamEudjEuUG9zdBI1CgpwYWdpbmF0aW9uGAIgASgLMiEuYmxvY2tuaW5qYS52MS5QYWdpbmF0aW9uUmVzcG9uc2UiFwoVTGlzdENhdGVnb3JpZXNSZXF1ZXN0IkUKFkxpc3RDYXRlZ29yaWVzUmVzcG9uc2USKwoKY2F0ZWdvcmllcxgBIAMoCzIXLmJsb2NrbmluamEudjEuQ2F0ZWdvcnkiIAoSR2V0Q2F0ZWdvcnlSZXF1ZXN0EgoKAmlkGAEgASgJIkAKE0dldENhdGVnb3J5UmVzcG9uc2USKQoIY2F0ZWdvcnkYASABKAsyFy5ibG9ja25pbmphLnYxLkNhdGVnb3J5InsKFUNyZWF0ZUNhdGVnb3J5UmVxdWVzdBIMCgRuYW1lGAEgASgJEgwKBHNsdWcYAiABKAkSGAoLZGVzY3JpcHRpb24YAyABKAlIAIgBARISCgVjb2xvchgEIAEoCUgBiAEBQg4KDF9kZXNjcmlwdGlvbkIICgZfY29sb3IiQwoWQ3JlYXRlQ2F0ZWdvcnlSZXNwb25zZRIpCghjYXRlZ29yeRgBIAEoCzIXLmJsb2NrbmluamEudjEuQ2F0ZWdvcnkiowEKFVVwZGF0ZUNhdGVnb3J5UmVxdWVzdBIKCgJpZBgBIAEoCRIRCgRuYW1lGAIgASgJSACIAQESEQoEc2x1ZxgDIAEoCUgBiAEBEhgKC2Rlc2NyaXB0aW9uGAQgASgJSAKIAQESEgoFY29sb3IYBSABKAlIA4gBAUIHCgVfbmFtZUIHCgVfc2x1Z0IOCgxfZGVzY3JpcHRpb25CCAoGX2NvbG9yIkMKFlVwZGF0ZUNhdGVnb3J5UmVzcG9uc2USKQoIY2F0ZWdvcnkYASABKAsyFy5ibG9ja25pbmphLnYxLkNhdGVnb3J5IiMKFURlbGV0ZUNhdGVnb3J5UmVxdWVzdBIKCgJpZBgBIAEoCSIYChZEZWxldGVDYXRlZ29yeVJlc3BvbnNlIisKGEdldFBvc3RDYXRlZ29yaWVzUmVxdWVzdBIPCgdwYWdlX2lkGAEgASgJIkgKGUdldFBvc3RDYXRlZ29yaWVzUmVzcG9uc2USKwoKY2F0ZWdvcmllcxgBIAMoCzIXLmJsb2NrbmluamEudjEuQ2F0ZWdvcnkiQAoYQWRkQ2F0ZWdvcnlUb1Bvc3RSZXF1ZXN0Eg8KB3BhZ2VfaWQYASABKAkSEwoLY2F0ZWdvcnlfaWQYAiABKAkiGwoZQWRkQ2F0ZWdvcnlUb1Bvc3RSZXNwb25zZSJFCh1SZW1vdmVDYXRlZ29yeUZyb21Qb3N0UmVxdWVzdBIPCgdwYWdlX2lkGAEgASgJEhMKC2NhdGVnb3J5X2lkGAIgASgJIiAKHlJlbW92ZUNhdGVnb3J5RnJvbVBvc3RSZXNwb25zZSI8ChlUb2dnbGVGZWF0dXJlZFBvc3RSZXF1ZXN0EgoKAmlkGAEgASgJEhMKC2lzX2ZlYXR1cmVkGAIgASgIIj8KGlRvZ2dsZUZlYXR1cmVkUG9zdFJlc3BvbnNlEiEKBHBhZ2UYASABKAsyEy5ibG9ja25pbmphLnYxLlBhZ2UyugsKDFBvc3RzU2VydmljZRJOCglMaXN0UG9zdHMSHy5ibG9ja25pbmphLnYxLkxpc3RQb3N0c1JlcXVlc3QaIC5ibG9ja25pbmphLnYxLkxpc3RQb3N0c1Jlc3BvbnNlEkgKB0dldFBvc3QSHS5ibG9ja25pbmphLnYxLkdldFBvc3RSZXF1ZXN0Gh4uYmxvY2tuaW5qYS52MS5HZXRQb3N0UmVzcG9uc2USYAoPTGlzdFBvc3RBcmNoaXZlEiUuYmxvY2tuaW5qYS52MS5MaXN0UG9zdEFyY2hpdmVSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5MaXN0UG9zdEFyY2hpdmVSZXNwb25zZRJjChBHZXRBcmNoaXZlQ291bnRzEiYuYmxvY2tuaW5qYS52MS5HZXRBcmNoaXZlQ291bnRzUmVxdWVzdBonLmJsb2NrbmluamEudjEuR2V0QXJjaGl2ZUNvdW50c1Jlc3BvbnNlEmAKD0dldFJlbGF0ZWRQb3N0cxIlLmJsb2NrbmluamEudjEuR2V0UmVsYXRlZFBvc3RzUmVxdWVzdBomLmJsb2NrbmluamEudjEuR2V0UmVsYXRlZFBvc3RzUmVzcG9uc2USYwoQR2V0UG9zdHNCeUF1dGhvchImLmJsb2NrbmluamEudjEuR2V0UG9zdHNCeUF1dGhvclJlcXVlc3QaJy5ibG9ja25pbmphLnYxLkdldFBvc3RzQnlBdXRob3JSZXNwb25zZRJdCg5MaXN0Q2F0ZWdvcmllcxIkLmJsb2NrbmluamEudjEuTGlzdENhdGVnb3JpZXNSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5MaXN0Q2F0ZWdvcmllc1Jlc3BvbnNlElQKC0dldENhdGVnb3J5EiEuYmxvY2tuaW5qYS52MS5HZXRDYXRlZ29yeVJlcXVlc3QaIi5ibG9ja25pbmphLnYxLkdldENhdGVnb3J5UmVzcG9uc2USXQoOQ3JlYXRlQ2F0ZWdvcnkSJC5ibG9ja25pbmphLnYxLkNyZWF0ZUNhdGVnb3J5UmVxdWVzdBolLmJsb2NrbmluamEudjEuQ3JlYXRlQ2F0ZWdvcnlSZXNwb25zZRJdCg5VcGRhdGVDYXRlZ29yeRIkLmJsb2NrbmluamEudjEuVXBkYXRlQ2F0ZWdvcnlSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5VcGRhdGVDYXRlZ29yeVJlc3BvbnNlEl0KDkRlbGV0ZUNhdGVnb3J5EiQuYmxvY2tuaW5qYS52MS5EZWxldGVDYXRlZ29yeVJlcXVlc3QaJS5ibG9ja25pbmphLnYxLkRlbGV0ZUNhdGVnb3J5UmVzcG9uc2USZgoRR2V0UG9zdENhdGVnb3JpZXMSJy5ibG9ja25pbmphLnYxLkdldFBvc3RDYXRlZ29yaWVzUmVxdWVzdBooLmJsb2NrbmluamEudjEuR2V0UG9zdENhdGVnb3JpZXNSZXNwb25zZRJmChFBZGRDYXRlZ29yeVRvUG9zdBInLmJsb2NrbmluamEudjEuQWRkQ2F0ZWdvcnlUb1Bvc3RSZXF1ZXN0GiguYmxvY2tuaW5qYS52MS5BZGRDYXRlZ29yeVRvUG9zdFJlc3BvbnNlEnUKFlJlbW92ZUNhdGVnb3J5RnJvbVBvc3QSLC5ibG9ja25pbmphLnYxLlJlbW92ZUNhdGVnb3J5RnJvbVBvc3RSZXF1ZXN0Gi0uYmxvY2tuaW5qYS52MS5SZW1vdmVDYXRlZ29yeUZyb21Qb3N0UmVzcG9uc2USaQoSVG9nZ2xlRmVhdHVyZWRQb3N0EiguYmxvY2tuaW5qYS52MS5Ub2dnbGVGZWF0dXJlZFBvc3RSZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5Ub2dnbGVGZWF0dXJlZFBvc3RSZXNwb25zZULAAQoRY29tLmJsb2NrbmluamEudjFCClBvc3RzUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_blockninja_v1_common, file_blockninja_v1_pages, file_google_protobuf_timestamp], +); +/** + * @generated from service blockninja.v1.PostsService + */ +const PostsService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_posts, 0); + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/blog_posts.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/blog_posts.proto. + */ +const file_blockninja_v1_blog_posts = /*@__PURE__*/ fileDesc( + "Ch5ibG9ja25pbmphL3YxL2Jsb2dfcG9zdHMucHJvdG8SDWJsb2NrbmluamEudjEi4QgKCEJsb2dQb3N0EgoKAmlkGAEgASgJEgwKBHNsdWcYAiABKAkSDQoFdGl0bGUYAyABKAkSKQoIZG9jdW1lbnQYBCABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0EikKBnN0YXR1cxgFIAEoDjIZLmJsb2NrbmluamEudjEuUGFnZVN0YXR1cxIPCgd2ZXJzaW9uGAYgASgFEjUKDHB1Ymxpc2hlZF9hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIAIgBARIuCgpjcmVhdGVkX2F0GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAkgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIXCgpjcmVhdGVkX2J5GAogASgJSAGIAQESFwoKdXBkYXRlZF9ieRgLIAEoCUgCiAEBEhQKB2V4Y2VycHQYDCABKAlIA4gBARIeChFmZWF0dXJlZF9pbWFnZV9pZBgNIAEoCUgEiAEBEh8KEmZlYXR1cmVkX2ltYWdlX3VybBgOIAEoCUgFiAEBEh4KEWF1dGhvcl9wcm9maWxlX2lkGA8gASgJSAaIAQESGAoLYXV0aG9yX25hbWUYECABKAlIB4gBARIYCgthdXRob3Jfc2x1ZxgRIAEoCUgIiAEBEhoKDWF1dGhvcl9hdmF0YXIYEiABKAlICYgBARIhChRyZWFkaW5nX3RpbWVfbWludXRlcxgTIAEoBUgKiAEBEhMKC2lzX2ZlYXR1cmVkGBQgASgIEjIKCXBvc3RfZGF0ZRgVIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIC4gBARIeChFwdWJsaXNoZWRfdmVyc2lvbhgWIAEoBUgMiAEBEh8KF2hhc191bnB1Ymxpc2hlZF9jaGFuZ2VzGBcgASgIEjcKDnVucHVibGlzaGVkX2F0GBsgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgNiAEBEhYKDmNhdGVnb3J5X2NvdW50GBggASgFEhQKDGlzX3B1Ymxpc2hlZBgZIAEoCBIUCgxpc19zY2hlZHVsZWQYGiABKAgSCwoDdXJsGBwgASgJQg8KDV9wdWJsaXNoZWRfYXRCDQoLX2NyZWF0ZWRfYnlCDQoLX3VwZGF0ZWRfYnlCCgoIX2V4Y2VycHRCFAoSX2ZlYXR1cmVkX2ltYWdlX2lkQhUKE19mZWF0dXJlZF9pbWFnZV91cmxCFAoSX2F1dGhvcl9wcm9maWxlX2lkQg4KDF9hdXRob3JfbmFtZUIOCgxfYXV0aG9yX3NsdWdCEAoOX2F1dGhvcl9hdmF0YXJCFwoVX3JlYWRpbmdfdGltZV9taW51dGVzQgwKCl9wb3N0X2RhdGVCFAoSX3B1Ymxpc2hlZF92ZXJzaW9uQhEKD191bnB1Ymxpc2hlZF9hdCLzAQoPQmxvZ1Bvc3RWZXJzaW9uEgoKAmlkGAEgASgJEg8KB3Bvc3RfaWQYAiABKAkSDwoHdmVyc2lvbhgDIAEoBRINCgV0aXRsZRgEIAEoCRIpCghkb2N1bWVudBgFIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSMAoMcHVibGlzaGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIZCgxwdWJsaXNoZWRfYnkYByABKAlIAIgBARIRCgRuYW1lGAggASgJSAGIAQFCDwoNX3B1Ymxpc2hlZF9ieUIHCgVfbmFtZSKCAwoUTGlzdEJsb2dQb3N0c1JlcXVlc3QSLQoKcGFnaW5hdGlvbhgBIAEoCzIZLmJsb2NrbmluamEudjEuUGFnaW5hdGlvbhI1Cg1zdGF0dXNfZmlsdGVyGAIgASgOMhkuYmxvY2tuaW5qYS52MS5QYWdlU3RhdHVzSACIAQESEwoGc2VhcmNoGAMgASgJSAGIAQESGAoLY2F0ZWdvcnlfaWQYBCABKAlIAogBARIeChFhdXRob3JfcHJvZmlsZV9pZBgFIAEoCUgDiAEBEhgKC2lzX2ZlYXR1cmVkGAYgASgISASIAQESFQoIb3JkZXJfYnkYByABKAlIBYgBARIWCglvcmRlcl9kaXIYCCABKAlIBogBAUIQCg5fc3RhdHVzX2ZpbHRlckIJCgdfc2VhcmNoQg4KDF9jYXRlZ29yeV9pZEIUChJfYXV0aG9yX3Byb2ZpbGVfaWRCDgoMX2lzX2ZlYXR1cmVkQgsKCV9vcmRlcl9ieUIMCgpfb3JkZXJfZGlyInYKFUxpc3RCbG9nUG9zdHNSZXNwb25zZRImCgVwb3N0cxgBIAMoCzIXLmJsb2NrbmluamEudjEuQmxvZ1Bvc3QSNQoKcGFnaW5hdGlvbhgCIAEoCzIhLmJsb2NrbmluamEudjEuUGFnaW5hdGlvblJlc3BvbnNlIiAKEkdldEJsb2dQb3N0UmVxdWVzdBIKCgJpZBgBIAEoCSI8ChNHZXRCbG9nUG9zdFJlc3BvbnNlEiUKBHBvc3QYASABKAsyFy5ibG9ja25pbmphLnYxLkJsb2dQb3N0Iu8CChVDcmVhdGVCbG9nUG9zdFJlcXVlc3QSDAoEc2x1ZxgBIAEoCRINCgV0aXRsZRgCIAEoCRIpCghkb2N1bWVudBgDIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSFAoHZXhjZXJwdBgEIAEoCUgAiAEBEh4KEWZlYXR1cmVkX2ltYWdlX2lkGAUgASgJSAGIAQESHgoRYXV0aG9yX3Byb2ZpbGVfaWQYBiABKAlIAogBARIYCgtpc19mZWF0dXJlZBgHIAEoCEgDiAEBEjIKCXBvc3RfZGF0ZRgIIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIBIgBARIUCgxjYXRlZ29yeV9pZHMYCSADKAlCCgoIX2V4Y2VycHRCFAoSX2ZlYXR1cmVkX2ltYWdlX2lkQhQKEl9hdXRob3JfcHJvZmlsZV9pZEIOCgxfaXNfZmVhdHVyZWRCDAoKX3Bvc3RfZGF0ZSI/ChZDcmVhdGVCbG9nUG9zdFJlc3BvbnNlEiUKBHBvc3QYASABKAsyFy5ibG9ja25pbmphLnYxLkJsb2dQb3N0Iq0EChVVcGRhdGVCbG9nUG9zdFJlcXVlc3QSCgoCaWQYASABKAkSEQoEc2x1ZxgCIAEoCUgAiAEBEhIKBXRpdGxlGAMgASgJSAGIAQESLgoIZG9jdW1lbnQYBCABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0SAKIAQESFAoHZXhjZXJwdBgFIAEoCUgDiAEBEh4KEWZlYXR1cmVkX2ltYWdlX2lkGAYgASgJSASIAQESIQoUY2xlYXJfZmVhdHVyZWRfaW1hZ2UYByABKAhIBYgBARIeChFhdXRob3JfcHJvZmlsZV9pZBgIIAEoCUgGiAEBEhkKDGNsZWFyX2F1dGhvchgJIAEoCEgHiAEBEhgKC2lzX2ZlYXR1cmVkGAogASgISAiIAQESMgoJcG9zdF9kYXRlGAsgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgJiAEBEhQKDGNhdGVnb3J5X2lkcxgMIAMoCRIZChF1cGRhdGVfY2F0ZWdvcmllcxgNIAEoCEIHCgVfc2x1Z0IICgZfdGl0bGVCCwoJX2RvY3VtZW50QgoKCF9leGNlcnB0QhQKEl9mZWF0dXJlZF9pbWFnZV9pZEIXChVfY2xlYXJfZmVhdHVyZWRfaW1hZ2VCFAoSX2F1dGhvcl9wcm9maWxlX2lkQg8KDV9jbGVhcl9hdXRob3JCDgoMX2lzX2ZlYXR1cmVkQgwKCl9wb3N0X2RhdGUiPwoWVXBkYXRlQmxvZ1Bvc3RSZXNwb25zZRIlCgRwb3N0GAEgASgLMhcuYmxvY2tuaW5qYS52MS5CbG9nUG9zdCIjChVEZWxldGVCbG9nUG9zdFJlcXVlc3QSCgoCaWQYASABKAkiGAoWRGVsZXRlQmxvZ1Bvc3RSZXNwb25zZSIkChZQdWJsaXNoQmxvZ1Bvc3RSZXF1ZXN0EgoKAmlkGAEgASgJIkAKF1B1Ymxpc2hCbG9nUG9zdFJlc3BvbnNlEiUKBHBvc3QYASABKAsyFy5ibG9ja25pbmphLnYxLkJsb2dQb3N0IiYKGFVucHVibGlzaEJsb2dQb3N0UmVxdWVzdBIKCgJpZBgBIAEoCSJCChlVbnB1Ymxpc2hCbG9nUG9zdFJlc3BvbnNlEiUKBHBvc3QYASABKAsyFy5ibG9ja25pbmphLnYxLkJsb2dQb3N0IlUKF1NjaGVkdWxlQmxvZ1Bvc3RSZXF1ZXN0EgoKAmlkGAEgASgJEi4KCnB1Ymxpc2hfYXQYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIkEKGFNjaGVkdWxlQmxvZ1Bvc3RSZXNwb25zZRIlCgRwb3N0GAEgASgLMhcuYmxvY2tuaW5qYS52MS5CbG9nUG9zdCInChlVbnNjaGVkdWxlQmxvZ1Bvc3RSZXF1ZXN0EgoKAmlkGAEgASgJIkMKGlVuc2NoZWR1bGVCbG9nUG9zdFJlc3BvbnNlEiUKBHBvc3QYASABKAsyFy5ibG9ja25pbmphLnYxLkJsb2dQb3N0Il0KG0xpc3RCbG9nUG9zdFZlcnNpb25zUmVxdWVzdBIPCgdwb3N0X2lkGAEgASgJEi0KCnBhZ2luYXRpb24YAiABKAsyGS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb24ihwEKHExpc3RCbG9nUG9zdFZlcnNpb25zUmVzcG9uc2USMAoIdmVyc2lvbnMYASADKAsyHi5ibG9ja25pbmphLnYxLkJsb2dQb3N0VmVyc2lvbhI1CgpwYWdpbmF0aW9uGAIgASgLMiEuYmxvY2tuaW5qYS52MS5QYWdpbmF0aW9uUmVzcG9uc2UiOwoXUm9sbGJhY2tCbG9nUG9zdFJlcXVlc3QSDwoHcG9zdF9pZBgBIAEoCRIPCgd2ZXJzaW9uGAIgASgFIkEKGFJvbGxiYWNrQmxvZ1Bvc3RSZXNwb25zZRIlCgRwb3N0GAEgASgLMhcuYmxvY2tuaW5qYS52MS5CbG9nUG9zdCJSCiBVcGRhdGVCbG9nUG9zdFZlcnNpb25OYW1lUmVxdWVzdBIPCgdwb3N0X2lkGAEgASgJEg8KB3ZlcnNpb24YAiABKAUSDAoEbmFtZRgDIAEoCSJUCiFVcGRhdGVCbG9nUG9zdFZlcnNpb25OYW1lUmVzcG9uc2USLwoHdmVyc2lvbhgBIAEoCzIeLmJsb2NrbmluamEudjEuQmxvZ1Bvc3RWZXJzaW9uIi8KHEdldEJsb2dQb3N0Q2F0ZWdvcmllc1JlcXVlc3QSDwoHcG9zdF9pZBgBIAEoCSJMCh1HZXRCbG9nUG9zdENhdGVnb3JpZXNSZXNwb25zZRIrCgpjYXRlZ29yaWVzGAEgAygLMhcuYmxvY2tuaW5qYS52MS5DYXRlZ29yeSJFChxTZXRCbG9nUG9zdENhdGVnb3JpZXNSZXF1ZXN0Eg8KB3Bvc3RfaWQYASABKAkSFAoMY2F0ZWdvcnlfaWRzGAIgAygJIkwKHVNldEJsb2dQb3N0Q2F0ZWdvcmllc1Jlc3BvbnNlEisKCmNhdGVnb3JpZXMYASADKAsyFy5ibG9ja25pbmphLnYxLkNhdGVnb3J5IikKGkJ1bGtEZWxldGVCbG9nUG9zdHNSZXF1ZXN0EgsKA2lkcxgBIAMoCSJ/ChtCdWxrRGVsZXRlQmxvZ1Bvc3RzUmVzcG9uc2USMwoHcmVzdWx0cxgBIAMoCzIiLmJsb2NrbmluamEudjEuQnVsa09wZXJhdGlvblJlc3VsdBIVCg1kZWxldGVkX2NvdW50GAIgASgFEhQKDGZhaWxlZF9jb3VudBgDIAEoBSIqChtCdWxrUHVibGlzaEJsb2dQb3N0c1JlcXVlc3QSCwoDaWRzGAEgAygJIoIBChxCdWxrUHVibGlzaEJsb2dQb3N0c1Jlc3BvbnNlEjMKB3Jlc3VsdHMYASADKAsyIi5ibG9ja25pbmphLnYxLkJ1bGtPcGVyYXRpb25SZXN1bHQSFwoPcHVibGlzaGVkX2NvdW50GAIgASgFEhQKDGZhaWxlZF9jb3VudBgDIAEoBSJACh1Ub2dnbGVCbG9nUG9zdEZlYXR1cmVkUmVxdWVzdBIKCgJpZBgBIAEoCRITCgtpc19mZWF0dXJlZBgCIAEoCCJHCh5Ub2dnbGVCbG9nUG9zdEZlYXR1cmVkUmVzcG9uc2USJQoEcG9zdBgBIAEoCzIXLmJsb2NrbmluamEudjEuQmxvZ1Bvc3Qyjw4KEEJsb2dQb3N0c1NlcnZpY2USWgoNTGlzdEJsb2dQb3N0cxIjLmJsb2NrbmluamEudjEuTGlzdEJsb2dQb3N0c1JlcXVlc3QaJC5ibG9ja25pbmphLnYxLkxpc3RCbG9nUG9zdHNSZXNwb25zZRJUCgtHZXRCbG9nUG9zdBIhLmJsb2NrbmluamEudjEuR2V0QmxvZ1Bvc3RSZXF1ZXN0GiIuYmxvY2tuaW5qYS52MS5HZXRCbG9nUG9zdFJlc3BvbnNlEl0KDkNyZWF0ZUJsb2dQb3N0EiQuYmxvY2tuaW5qYS52MS5DcmVhdGVCbG9nUG9zdFJlcXVlc3QaJS5ibG9ja25pbmphLnYxLkNyZWF0ZUJsb2dQb3N0UmVzcG9uc2USXQoOVXBkYXRlQmxvZ1Bvc3QSJC5ibG9ja25pbmphLnYxLlVwZGF0ZUJsb2dQb3N0UmVxdWVzdBolLmJsb2NrbmluamEudjEuVXBkYXRlQmxvZ1Bvc3RSZXNwb25zZRJdCg5EZWxldGVCbG9nUG9zdBIkLmJsb2NrbmluamEudjEuRGVsZXRlQmxvZ1Bvc3RSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5EZWxldGVCbG9nUG9zdFJlc3BvbnNlEmAKD1B1Ymxpc2hCbG9nUG9zdBIlLmJsb2NrbmluamEudjEuUHVibGlzaEJsb2dQb3N0UmVxdWVzdBomLmJsb2NrbmluamEudjEuUHVibGlzaEJsb2dQb3N0UmVzcG9uc2USZgoRVW5wdWJsaXNoQmxvZ1Bvc3QSJy5ibG9ja25pbmphLnYxLlVucHVibGlzaEJsb2dQb3N0UmVxdWVzdBooLmJsb2NrbmluamEudjEuVW5wdWJsaXNoQmxvZ1Bvc3RSZXNwb25zZRJjChBTY2hlZHVsZUJsb2dQb3N0EiYuYmxvY2tuaW5qYS52MS5TY2hlZHVsZUJsb2dQb3N0UmVxdWVzdBonLmJsb2NrbmluamEudjEuU2NoZWR1bGVCbG9nUG9zdFJlc3BvbnNlEmkKElVuc2NoZWR1bGVCbG9nUG9zdBIoLmJsb2NrbmluamEudjEuVW5zY2hlZHVsZUJsb2dQb3N0UmVxdWVzdBopLmJsb2NrbmluamEudjEuVW5zY2hlZHVsZUJsb2dQb3N0UmVzcG9uc2USbwoUTGlzdEJsb2dQb3N0VmVyc2lvbnMSKi5ibG9ja25pbmphLnYxLkxpc3RCbG9nUG9zdFZlcnNpb25zUmVxdWVzdBorLmJsb2NrbmluamEudjEuTGlzdEJsb2dQb3N0VmVyc2lvbnNSZXNwb25zZRJjChBSb2xsYmFja0Jsb2dQb3N0EiYuYmxvY2tuaW5qYS52MS5Sb2xsYmFja0Jsb2dQb3N0UmVxdWVzdBonLmJsb2NrbmluamEudjEuUm9sbGJhY2tCbG9nUG9zdFJlc3BvbnNlEn4KGVVwZGF0ZUJsb2dQb3N0VmVyc2lvbk5hbWUSLy5ibG9ja25pbmphLnYxLlVwZGF0ZUJsb2dQb3N0VmVyc2lvbk5hbWVSZXF1ZXN0GjAuYmxvY2tuaW5qYS52MS5VcGRhdGVCbG9nUG9zdFZlcnNpb25OYW1lUmVzcG9uc2UScgoVR2V0QmxvZ1Bvc3RDYXRlZ29yaWVzEisuYmxvY2tuaW5qYS52MS5HZXRCbG9nUG9zdENhdGVnb3JpZXNSZXF1ZXN0GiwuYmxvY2tuaW5qYS52MS5HZXRCbG9nUG9zdENhdGVnb3JpZXNSZXNwb25zZRJyChVTZXRCbG9nUG9zdENhdGVnb3JpZXMSKy5ibG9ja25pbmphLnYxLlNldEJsb2dQb3N0Q2F0ZWdvcmllc1JlcXVlc3QaLC5ibG9ja25pbmphLnYxLlNldEJsb2dQb3N0Q2F0ZWdvcmllc1Jlc3BvbnNlEmwKE0J1bGtEZWxldGVCbG9nUG9zdHMSKS5ibG9ja25pbmphLnYxLkJ1bGtEZWxldGVCbG9nUG9zdHNSZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5CdWxrRGVsZXRlQmxvZ1Bvc3RzUmVzcG9uc2USbwoUQnVsa1B1Ymxpc2hCbG9nUG9zdHMSKi5ibG9ja25pbmphLnYxLkJ1bGtQdWJsaXNoQmxvZ1Bvc3RzUmVxdWVzdBorLmJsb2NrbmluamEudjEuQnVsa1B1Ymxpc2hCbG9nUG9zdHNSZXNwb25zZRJ1ChZUb2dnbGVCbG9nUG9zdEZlYXR1cmVkEiwuYmxvY2tuaW5qYS52MS5Ub2dnbGVCbG9nUG9zdEZlYXR1cmVkUmVxdWVzdBotLmJsb2NrbmluamEudjEuVG9nZ2xlQmxvZ1Bvc3RGZWF0dXJlZFJlc3BvbnNlQsQBChFjb20uYmxvY2tuaW5qYS52MUIOQmxvZ1Bvc3RzUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_blockninja_v1_common, file_blockninja_v1_pages, file_blockninja_v1_posts, file_google_protobuf_timestamp, file_google_protobuf_struct], +); +/** + * BlogPostsService handles blog post operations + * Blog posts are stored in a dedicated blog_posts table and render via + * the blog_post_template system page by default (no individual blocks). + * To customize a specific post's layout, create a regular page at the same slug. + * + * @generated from service blockninja.v1.BlogPostsService + */ +const BlogPostsService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_blog_posts, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/blog_posts.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * CRUD operations + * + * @generated from rpc blockninja.v1.BlogPostsService.ListBlogPosts + */ +BlogPostsService.method.listBlogPosts; +/** + * @generated from rpc blockninja.v1.BlogPostsService.GetBlogPost + */ +BlogPostsService.method.getBlogPost; +/** + * @generated from rpc blockninja.v1.BlogPostsService.CreateBlogPost + */ +BlogPostsService.method.createBlogPost; +/** + * @generated from rpc blockninja.v1.BlogPostsService.UpdateBlogPost + */ +BlogPostsService.method.updateBlogPost; +/** + * @generated from rpc blockninja.v1.BlogPostsService.DeleteBlogPost + */ +BlogPostsService.method.deleteBlogPost; +/** + * Publishing + * + * @generated from rpc blockninja.v1.BlogPostsService.PublishBlogPost + */ +BlogPostsService.method.publishBlogPost; +/** + * @generated from rpc blockninja.v1.BlogPostsService.UnpublishBlogPost + */ +BlogPostsService.method.unpublishBlogPost; +/** + * @generated from rpc blockninja.v1.BlogPostsService.ScheduleBlogPost + */ +BlogPostsService.method.scheduleBlogPost; +/** + * @generated from rpc blockninja.v1.BlogPostsService.UnscheduleBlogPost + */ +BlogPostsService.method.unscheduleBlogPost; +/** + * Versioning + * + * @generated from rpc blockninja.v1.BlogPostsService.ListBlogPostVersions + */ +BlogPostsService.method.listBlogPostVersions; +/** + * @generated from rpc blockninja.v1.BlogPostsService.RollbackBlogPost + */ +BlogPostsService.method.rollbackBlogPost; +/** + * @generated from rpc blockninja.v1.BlogPostsService.UpdateBlogPostVersionName + */ +BlogPostsService.method.updateBlogPostVersionName; +/** + * Categories + * + * @generated from rpc blockninja.v1.BlogPostsService.GetBlogPostCategories + */ +BlogPostsService.method.getBlogPostCategories; +/** + * @generated from rpc blockninja.v1.BlogPostsService.SetBlogPostCategories + */ +BlogPostsService.method.setBlogPostCategories; +/** + * Bulk operations + * + * @generated from rpc blockninja.v1.BlogPostsService.BulkDeleteBlogPosts + */ +BlogPostsService.method.bulkDeleteBlogPosts; +/** + * @generated from rpc blockninja.v1.BlogPostsService.BulkPublishBlogPosts + */ +BlogPostsService.method.bulkPublishBlogPosts; +/** + * Featured posts toggle + * + * @generated from rpc blockninja.v1.BlogPostsService.ToggleBlogPostFeatured + */ +BlogPostsService.method.toggleBlogPostFeatured; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/brand.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/brand.proto. + */ +const file_blockninja_v1_brand = /*@__PURE__*/ fileDesc( + "ChlibG9ja25pbmphL3YxL2JyYW5kLnByb3RvEg1ibG9ja25pbmphLnYxIvUBCgpCcmFuZFN0eWxlEhgKEGJhY2tncm91bmRfY29sb3IYASABKAkSIAoTYmFja2dyb3VuZF9ncmFkaWVudBgCIAEoCUgAiAEBEigKBXNoYXBlGAMgASgOMhkuYmxvY2tuaW5qYS52MS5CcmFuZFNoYXBlEhUKDWJvcmRlcl9yYWRpdXMYBCABKAUSDwoHcGFkZGluZxgFIAEoBRINCgVzY2FsZRgGIAEoBRIdChBmb3JlZ3JvdW5kX2NvbG9yGAcgASgJSAGIAQFCFgoUX2JhY2tncm91bmRfZ3JhZGllbnRCEwoRX2ZvcmVncm91bmRfY29sb3Ii9QQKCUJyYW5kU3BlYxIKCgJpZBgBIAEoCRIPCgd2ZXJzaW9uGAIgASgFEhEKCWlzX2FjdGl2ZRgDIAEoCBIzCgtzb3VyY2VfdHlwZRgEIAEoDjIeLmJsb2NrbmluamEudjEuQnJhbmRTb3VyY2VUeXBlEhwKD3NvdXJjZV9tZWRpYV9pZBgFIAEoCUgAiAEBEhsKDnNvdXJjZV9jb250ZW50GAYgASgJSAGIAQESGAoLc291cmNlX2ZvbnQYByABKAlIAogBARIoCgVzdHlsZRgIIAEoCzIZLmJsb2NrbmluamEudjEuQnJhbmRTdHlsZRIyCgpkYXJrX3N0eWxlGAkgASgLMhkuYmxvY2tuaW5qYS52MS5CcmFuZFN0eWxlSAOIAQESEwoLdGhlbWVfY29sb3IYCiABKAkSGQoRZ2VuZXJhdGlvbl9zdGF0dXMYCyABKAkSHQoQZ2VuZXJhdGlvbl9lcnJvchgMIAEoCUgEiAEBEjUKDGdlbmVyYXRlZF9hdBgNIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIBYgBARIuCgpjcmVhdGVkX2F0GA4gASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GA8gASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEISChBfc291cmNlX21lZGlhX2lkQhEKD19zb3VyY2VfY29udGVudEIOCgxfc291cmNlX2ZvbnRCDQoLX2Rhcmtfc3R5bGVCEwoRX2dlbmVyYXRpb25fZXJyb3JCDwoNX2dlbmVyYXRlZF9hdCJfCgpCcmFuZEFzc2V0EhIKCmFzc2V0X3R5cGUYASABKAkSCwoDdXJsGAIgASgJEg0KBXdpZHRoGAMgASgFEg4KBmhlaWdodBgEIAEoBRIRCgltaW1lX3R5cGUYBSABKAkilQIKDEJyYW5kaW5nRGF0YRITCgtmYXZpY29uX2ljbxgBIAEoCRISCgpmYXZpY29uXzE2GAIgASgJEhIKCmZhdmljb25fMzIYAyABKAkSGAoQYXBwbGVfdG91Y2hfaWNvbhgEIAEoCRITCgthbmRyb2lkXzE5MhgFIAEoCRITCgthbmRyb2lkXzUxMhgGIAEoCRIUCgxtYXNrYWJsZV81MTIYByABKAkSEwoLbWFzdGVyXzEwMjQYCCABKAkSFAoMbWFuaWZlc3RfdXJsGAkgASgJEhMKC3RoZW1lX2NvbG9yGAogASgJEhAKA3N2ZxgLIAEoCUgAiAEBEhQKDGlzX2dlbmVyYXRlZBgMIAEoCEIGCgRfc3ZnIhUKE0dldEJyYW5kU3BlY1JlcXVlc3QiTAoUR2V0QnJhbmRTcGVjUmVzcG9uc2USKwoEc3BlYxgBIAEoCzIYLmJsb2NrbmluamEudjEuQnJhbmRTcGVjSACIAQFCBwoFX3NwZWMi8AIKFFNhdmVCcmFuZFNwZWNSZXF1ZXN0EjMKC3NvdXJjZV90eXBlGAEgASgOMh4uYmxvY2tuaW5qYS52MS5CcmFuZFNvdXJjZVR5cGUSHAoPc291cmNlX21lZGlhX2lkGAIgASgJSACIAQESGwoOc291cmNlX2NvbnRlbnQYAyABKAlIAYgBARIYCgtzb3VyY2VfZm9udBgEIAEoCUgCiAEBEigKBXN0eWxlGAUgASgLMhkuYmxvY2tuaW5qYS52MS5CcmFuZFN0eWxlEjIKCmRhcmtfc3R5bGUYBiABKAsyGS5ibG9ja25pbmphLnYxLkJyYW5kU3R5bGVIA4gBARITCgt0aGVtZV9jb2xvchgHIAEoCRIVCg1hdXRvX2dlbmVyYXRlGAggASgIQhIKEF9zb3VyY2VfbWVkaWFfaWRCEQoPX3NvdXJjZV9jb250ZW50Qg4KDF9zb3VyY2VfZm9udEINCgtfZGFya19zdHlsZSI/ChVTYXZlQnJhbmRTcGVjUmVzcG9uc2USJgoEc3BlYxgBIAEoCzIYLmJsb2NrbmluamEudjEuQnJhbmRTcGVjIhwKGkdlbmVyYXRlQnJhbmRBc3NldHNSZXF1ZXN0InAKG0dlbmVyYXRlQnJhbmRBc3NldHNSZXNwb25zZRImCgRzcGVjGAEgASgLMhguYmxvY2tuaW5qYS52MS5CcmFuZFNwZWMSKQoGYXNzZXRzGAIgAygLMhkuYmxvY2tuaW5qYS52MS5CcmFuZEFzc2V0IhcKFUdldEJyYW5kQXNzZXRzUmVxdWVzdCJHChZHZXRCcmFuZEFzc2V0c1Jlc3BvbnNlEi0KCGJyYW5kaW5nGAEgASgLMhsuYmxvY2tuaW5qYS52MS5CcmFuZGluZ0RhdGEiLQocTGlzdEJyYW5kU3BlY1ZlcnNpb25zUmVxdWVzdBINCgVsaW1pdBgBIAEoBSJLCh1MaXN0QnJhbmRTcGVjVmVyc2lvbnNSZXNwb25zZRIqCgh2ZXJzaW9ucxgBIAMoCzIYLmJsb2NrbmluamEudjEuQnJhbmRTcGVjIikKFlJldmVydEJyYW5kU3BlY1JlcXVlc3QSDwoHdmVyc2lvbhgBIAEoBSJBChdSZXZlcnRCcmFuZFNwZWNSZXNwb25zZRImCgRzcGVjGAEgASgLMhguYmxvY2tuaW5qYS52MS5CcmFuZFNwZWMikgIKF1ByZXZpZXdCcmFuZEljb25SZXF1ZXN0EjMKC3NvdXJjZV90eXBlGAEgASgOMh4uYmxvY2tuaW5qYS52MS5CcmFuZFNvdXJjZVR5cGUSHAoPc291cmNlX21lZGlhX2lkGAIgASgJSACIAQESGwoOc291cmNlX2NvbnRlbnQYAyABKAlIAYgBARIYCgtzb3VyY2VfZm9udBgEIAEoCUgCiAEBEigKBXN0eWxlGAUgASgLMhkuYmxvY2tuaW5qYS52MS5CcmFuZFN0eWxlEgwKBHNpemUYBiABKAVCEgoQX3NvdXJjZV9tZWRpYV9pZEIRCg9fc291cmNlX2NvbnRlbnRCDgoMX3NvdXJjZV9mb250IkUKGFByZXZpZXdCcmFuZEljb25SZXNwb25zZRIWCg5wcmV2aWV3X2Jhc2U2NBgBIAEoCRIRCgltaW1lX3R5cGUYAiABKAkqiwEKD0JyYW5kU291cmNlVHlwZRIhCh1CUkFORF9TT1VSQ0VfVFlQRV9VTlNQRUNJRklFRBAAEhwKGEJSQU5EX1NPVVJDRV9UWVBFX1VQTE9BRBABEhsKF0JSQU5EX1NPVVJDRV9UWVBFX0VNT0pJEAISGgoWQlJBTkRfU09VUkNFX1RZUEVfVEVYVBADKnIKCkJyYW5kU2hhcGUSGwoXQlJBTkRfU0hBUEVfVU5TUEVDSUZJRUQQABIWChJCUkFORF9TSEFQRV9TUVVBUkUQARIXChNCUkFORF9TSEFQRV9ST1VOREVEEAISFgoSQlJBTkRfU0hBUEVfQ0lSQ0xFEAMyywUKDEJyYW5kU2VydmljZRJXCgxHZXRCcmFuZFNwZWMSIi5ibG9ja25pbmphLnYxLkdldEJyYW5kU3BlY1JlcXVlc3QaIy5ibG9ja25pbmphLnYxLkdldEJyYW5kU3BlY1Jlc3BvbnNlEloKDVNhdmVCcmFuZFNwZWMSIy5ibG9ja25pbmphLnYxLlNhdmVCcmFuZFNwZWNSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5TYXZlQnJhbmRTcGVjUmVzcG9uc2USbAoTR2VuZXJhdGVCcmFuZEFzc2V0cxIpLmJsb2NrbmluamEudjEuR2VuZXJhdGVCcmFuZEFzc2V0c1JlcXVlc3QaKi5ibG9ja25pbmphLnYxLkdlbmVyYXRlQnJhbmRBc3NldHNSZXNwb25zZRJdCg5HZXRCcmFuZEFzc2V0cxIkLmJsb2NrbmluamEudjEuR2V0QnJhbmRBc3NldHNSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5HZXRCcmFuZEFzc2V0c1Jlc3BvbnNlEnIKFUxpc3RCcmFuZFNwZWNWZXJzaW9ucxIrLmJsb2NrbmluamEudjEuTGlzdEJyYW5kU3BlY1ZlcnNpb25zUmVxdWVzdBosLmJsb2NrbmluamEudjEuTGlzdEJyYW5kU3BlY1ZlcnNpb25zUmVzcG9uc2USYAoPUmV2ZXJ0QnJhbmRTcGVjEiUuYmxvY2tuaW5qYS52MS5SZXZlcnRCcmFuZFNwZWNSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5SZXZlcnRCcmFuZFNwZWNSZXNwb25zZRJjChBQcmV2aWV3QnJhbmRJY29uEiYuYmxvY2tuaW5qYS52MS5QcmV2aWV3QnJhbmRJY29uUmVxdWVzdBonLmJsb2NrbmluamEudjEuUHJldmlld0JyYW5kSWNvblJlc3BvbnNlQsABChFjb20uYmxvY2tuaW5qYS52MUIKQnJhbmRQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_google_protobuf_timestamp], +); +/** + * Source types for site icon + * + * @generated from enum blockninja.v1.BrandSourceType + */ +var BrandSourceType; +(function (BrandSourceType) { + /** + * @generated from enum value: BRAND_SOURCE_TYPE_UNSPECIFIED = 0; + */ + BrandSourceType[(BrandSourceType["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * SVG or PNG uploaded via media + * + * @generated from enum value: BRAND_SOURCE_TYPE_UPLOAD = 1; + */ + BrandSourceType[(BrandSourceType["UPLOAD"] = 1)] = "UPLOAD"; + /** + * Unicode emoji character + * + * @generated from enum value: BRAND_SOURCE_TYPE_EMOJI = 2; + */ + BrandSourceType[(BrandSourceType["EMOJI"] = 2)] = "EMOJI"; + /** + * Text/initials + * + * @generated from enum value: BRAND_SOURCE_TYPE_TEXT = 3; + */ + BrandSourceType[(BrandSourceType["TEXT"] = 3)] = "TEXT"; +})(BrandSourceType || (BrandSourceType = {})); +/** + * Icon shape + * + * @generated from enum blockninja.v1.BrandShape + */ +var BrandShape; +(function (BrandShape) { + /** + * @generated from enum value: BRAND_SHAPE_UNSPECIFIED = 0; + */ + BrandShape[(BrandShape["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * @generated from enum value: BRAND_SHAPE_SQUARE = 1; + */ + BrandShape[(BrandShape["SQUARE"] = 1)] = "SQUARE"; + /** + * @generated from enum value: BRAND_SHAPE_ROUNDED = 2; + */ + BrandShape[(BrandShape["ROUNDED"] = 2)] = "ROUNDED"; + /** + * @generated from enum value: BRAND_SHAPE_CIRCLE = 3; + */ + BrandShape[(BrandShape["CIRCLE"] = 3)] = "CIRCLE"; +})(BrandShape || (BrandShape = {})); +/** + * @generated from service blockninja.v1.BrandService + */ +const BrandService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_brand, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/brand.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Get current active brand spec + * + * @generated from rpc blockninja.v1.BrandService.GetBrandSpec + */ +BrandService.method.getBrandSpec; +/** + * Save brand spec (create or update) + * + * @generated from rpc blockninja.v1.BrandService.SaveBrandSpec + */ +BrandService.method.saveBrandSpec; +/** + * Generate brand assets from current spec + * + * @generated from rpc blockninja.v1.BrandService.GenerateBrandAssets + */ +BrandService.method.generateBrandAssets; +/** + * Get generated brand assets (for template consumption) + * + * @generated from rpc blockninja.v1.BrandService.GetBrandAssets + */ +BrandService.method.getBrandAssets; +/** + * List brand spec versions for revert + * + * @generated from rpc blockninja.v1.BrandService.ListBrandSpecVersions + */ +BrandService.method.listBrandSpecVersions; +/** + * Revert to a previous version + * + * @generated from rpc blockninja.v1.BrandService.RevertBrandSpec + */ +BrandService.method.revertBrandSpec; +/** + * Preview brand icon (returns base64 PNG preview without saving) + * + * @generated from rpc blockninja.v1.BrandService.PreviewBrandIcon + */ +BrandService.method.previewBrandIcon; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/buckets.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/buckets.proto. + */ +const file_blockninja_v1_buckets = /*@__PURE__*/ fileDesc( + "ChtibG9ja25pbmphL3YxL2J1Y2tldHMucHJvdG8SDWJsb2NrbmluamEudjEiuQEKCkRhdGFzb3VyY2USCgoCaWQYASABKAkSDQoFdGl0bGUYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSEAoIY2F0ZWdvcnkYBCABKAkSGQoRcXVlcnlfc2NoZW1hX2pzb24YBSABKAkSGgoSb3V0cHV0X3NjaGVtYV9qc29uGAYgASgJEhgKEHN1cHBvcnRzX2N1cmF0ZWQYByABKAgSGAoQc3VwcG9ydHNfYmluZGluZxgIIAEoCCKGAwoGQnVja2V0EgoKAmlkGAEgASgJEhIKCmJ1Y2tldF9rZXkYAiABKAkSDAoEbmFtZRgDIAEoCRITCgtkZXNjcmlwdGlvbhgEIAEoCRIVCg1kYXRhc291cmNlX2lkGAUgASgJEicKBG1vZGUYBiABKA4yGS5ibG9ja25pbmphLnYxLkJ1Y2tldE1vZGUSEQoJZml4ZWRfaWRzGAcgAygJEiwKC3NhdmVkX3F1ZXJ5GAggASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIZChFjYWNoZV90dGxfc2Vjb25kcxgJIAEoBRIPCgd2ZXJzaW9uGAogASgFEi4KCmNyZWF0ZWRfYXQYCyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYDCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhUKDWNyZWF0ZWRfYnlfaWQYDSABKAkSFQoNdXBkYXRlZF9ieV9pZBgOIAEoCSKBAQoLQ3VyYXRlZEl0ZW0SCgoCaWQYASABKAkSDQoFdGl0bGUYAiABKAkSEAoIc3VidGl0bGUYAyABKAkSFQoNdGh1bWJuYWlsX3VybBgEIAEoCRIuCgpjcmVhdGVkX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCIqChZMaXN0RGF0YXNvdXJjZXNSZXF1ZXN0EhAKCGNhdGVnb3J5GAEgASgJIkkKF0xpc3REYXRhc291cmNlc1Jlc3BvbnNlEi4KC2RhdGFzb3VyY2VzGAEgAygLMhkuYmxvY2tuaW5qYS52MS5EYXRhc291cmNlIiIKFEdldERhdGFzb3VyY2VSZXF1ZXN0EgoKAmlkGAEgASgJIkYKFUdldERhdGFzb3VyY2VSZXNwb25zZRItCgpkYXRhc291cmNlGAEgASgLMhkuYmxvY2tuaW5qYS52MS5EYXRhc291cmNlIloKEkxpc3RCdWNrZXRzUmVxdWVzdBIVCg1kYXRhc291cmNlX2lkGAEgASgJEg4KBnNlYXJjaBgCIAEoCRINCgVsaW1pdBgDIAEoBRIOCgZvZmZzZXQYBCABKAUiTAoTTGlzdEJ1Y2tldHNSZXNwb25zZRImCgdidWNrZXRzGAEgAygLMhUuYmxvY2tuaW5qYS52MS5CdWNrZXQSDQoFdG90YWwYAiABKAUiHgoQR2V0QnVja2V0UmVxdWVzdBIKCgJpZBgBIAEoCSJpChFHZXRCdWNrZXRSZXNwb25zZRIlCgZidWNrZXQYASABKAsyFS5ibG9ja25pbmphLnYxLkJ1Y2tldBItCgpkYXRhc291cmNlGAIgASgLMhkuYmxvY2tuaW5qYS52MS5EYXRhc291cmNlIugBChNDcmVhdGVCdWNrZXRSZXF1ZXN0EhIKCmJ1Y2tldF9rZXkYASABKAkSDAoEbmFtZRgCIAEoCRITCgtkZXNjcmlwdGlvbhgDIAEoCRIVCg1kYXRhc291cmNlX2lkGAQgASgJEicKBG1vZGUYBSABKA4yGS5ibG9ja25pbmphLnYxLkJ1Y2tldE1vZGUSEQoJZml4ZWRfaWRzGAYgAygJEiwKC3NhdmVkX3F1ZXJ5GAcgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIZChFjYWNoZV90dGxfc2Vjb25kcxgIIAEoBSI9ChRDcmVhdGVCdWNrZXRSZXNwb25zZRIlCgZidWNrZXQYASABKAsyFS5ibG9ja25pbmphLnYxLkJ1Y2tldCKVAgoTVXBkYXRlQnVja2V0UmVxdWVzdBIKCgJpZBgBIAEoCRIRCgRuYW1lGAIgASgJSACIAQESGAoLZGVzY3JpcHRpb24YAyABKAlIAYgBARIsCgRtb2RlGAQgASgOMhkuYmxvY2tuaW5qYS52MS5CdWNrZXRNb2RlSAKIAQESEQoJZml4ZWRfaWRzGAUgAygJEiwKC3NhdmVkX3F1ZXJ5GAYgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIeChFjYWNoZV90dGxfc2Vjb25kcxgHIAEoBUgDiAEBQgcKBV9uYW1lQg4KDF9kZXNjcmlwdGlvbkIHCgVfbW9kZUIUChJfY2FjaGVfdHRsX3NlY29uZHMiPQoUVXBkYXRlQnVja2V0UmVzcG9uc2USJQoGYnVja2V0GAEgASgLMhUuYmxvY2tuaW5qYS52MS5CdWNrZXQiIQoTRGVsZXRlQnVja2V0UmVxdWVzdBIKCgJpZBgBIAEoCSIWChREZWxldGVCdWNrZXRSZXNwb25zZSJqChhQcmV2aWV3QnVja2V0RGF0YVJlcXVlc3QSEQoJYnVja2V0X2lkGAEgASgJEiwKC2JpbmRfcGFyYW1zGAIgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBINCgVsaW1pdBgDIAEoBSJSChlQcmV2aWV3QnVja2V0RGF0YVJlc3BvbnNlEiYKBWl0ZW1zGAEgAygLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBINCgV0b3RhbBgCIAEoBSJfChdMaXN0Q3VyYXRlZEl0ZW1zUmVxdWVzdBIVCg1kYXRhc291cmNlX2lkGAEgASgJEg4KBnNlYXJjaBgCIAEoCRINCgVsaW1pdBgDIAEoBRIOCgZvZmZzZXQYBCABKAUiVAoYTGlzdEN1cmF0ZWRJdGVtc1Jlc3BvbnNlEikKBWl0ZW1zGAEgAygLMhouYmxvY2tuaW5qYS52MS5DdXJhdGVkSXRlbRINCgV0b3RhbBgCIAEoBSIuChVHZXRRdWVyeVNjaGVtYVJlcXVlc3QSFQoNZGF0YXNvdXJjZV9pZBgBIAEoCSItChZHZXRRdWVyeVNjaGVtYVJlc3BvbnNlEhMKC3NjaGVtYV9qc29uGAEgASgJIloKGkdlbmVyYXRlQnVja2V0UXVlcnlSZXF1ZXN0EhUKDWRhdGFzb3VyY2VfaWQYASABKAkSDgoGcHJvbXB0GAIgASgJEhUKDWN1cnJlbnRfcXVlcnkYAyABKAkiSwobR2VuZXJhdGVCdWNrZXRRdWVyeVJlc3BvbnNlEhcKD3N1Z2dlc3RlZF9xdWVyeRgBIAEoCRITCgtleHBsYW5hdGlvbhgCIAEoCSIqChxJbnZhbGlkYXRlQnVja2V0Q2FjaGVSZXF1ZXN0EgoKAmlkGAEgASgJIjAKHUludmFsaWRhdGVCdWNrZXRDYWNoZVJlc3BvbnNlEg8KB3N1Y2Nlc3MYASABKAgqWQoKQnVja2V0TW9kZRIbChdCVUNLRVRfTU9ERV9VTlNQRUNJRklFRBAAEhcKE0JVQ0tFVF9NT0RFX0NVUkFURUQQARIVChFCVUNLRVRfTU9ERV9RVUVSWRACMo0JCg5CdWNrZXRzU2VydmljZRJgCg9MaXN0RGF0YXNvdXJjZXMSJS5ibG9ja25pbmphLnYxLkxpc3REYXRhc291cmNlc1JlcXVlc3QaJi5ibG9ja25pbmphLnYxLkxpc3REYXRhc291cmNlc1Jlc3BvbnNlEloKDUdldERhdGFzb3VyY2USIy5ibG9ja25pbmphLnYxLkdldERhdGFzb3VyY2VSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5HZXREYXRhc291cmNlUmVzcG9uc2USVAoLTGlzdEJ1Y2tldHMSIS5ibG9ja25pbmphLnYxLkxpc3RCdWNrZXRzUmVxdWVzdBoiLmJsb2NrbmluamEudjEuTGlzdEJ1Y2tldHNSZXNwb25zZRJOCglHZXRCdWNrZXQSHy5ibG9ja25pbmphLnYxLkdldEJ1Y2tldFJlcXVlc3QaIC5ibG9ja25pbmphLnYxLkdldEJ1Y2tldFJlc3BvbnNlElcKDENyZWF0ZUJ1Y2tldBIiLmJsb2NrbmluamEudjEuQ3JlYXRlQnVja2V0UmVxdWVzdBojLmJsb2NrbmluamEudjEuQ3JlYXRlQnVja2V0UmVzcG9uc2USVwoMVXBkYXRlQnVja2V0EiIuYmxvY2tuaW5qYS52MS5VcGRhdGVCdWNrZXRSZXF1ZXN0GiMuYmxvY2tuaW5qYS52MS5VcGRhdGVCdWNrZXRSZXNwb25zZRJXCgxEZWxldGVCdWNrZXQSIi5ibG9ja25pbmphLnYxLkRlbGV0ZUJ1Y2tldFJlcXVlc3QaIy5ibG9ja25pbmphLnYxLkRlbGV0ZUJ1Y2tldFJlc3BvbnNlEmYKEVByZXZpZXdCdWNrZXREYXRhEicuYmxvY2tuaW5qYS52MS5QcmV2aWV3QnVja2V0RGF0YVJlcXVlc3QaKC5ibG9ja25pbmphLnYxLlByZXZpZXdCdWNrZXREYXRhUmVzcG9uc2USYwoQTGlzdEN1cmF0ZWRJdGVtcxImLmJsb2NrbmluamEudjEuTGlzdEN1cmF0ZWRJdGVtc1JlcXVlc3QaJy5ibG9ja25pbmphLnYxLkxpc3RDdXJhdGVkSXRlbXNSZXNwb25zZRJdCg5HZXRRdWVyeVNjaGVtYRIkLmJsb2NrbmluamEudjEuR2V0UXVlcnlTY2hlbWFSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5HZXRRdWVyeVNjaGVtYVJlc3BvbnNlEmwKE0dlbmVyYXRlQnVja2V0UXVlcnkSKS5ibG9ja25pbmphLnYxLkdlbmVyYXRlQnVja2V0UXVlcnlSZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5HZW5lcmF0ZUJ1Y2tldFF1ZXJ5UmVzcG9uc2UScgoVSW52YWxpZGF0ZUJ1Y2tldENhY2hlEisuYmxvY2tuaW5qYS52MS5JbnZhbGlkYXRlQnVja2V0Q2FjaGVSZXF1ZXN0GiwuYmxvY2tuaW5qYS52MS5JbnZhbGlkYXRlQnVja2V0Q2FjaGVSZXNwb25zZULCAQoRY29tLmJsb2NrbmluamEudjFCDEJ1Y2tldHNQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_google_protobuf_struct, file_google_protobuf_timestamp, file_blockninja_v1_common], +); +/** + * BucketMode distinguishes curated (fixed list) from query (dynamic) + * + * @generated from enum blockninja.v1.BucketMode + */ +var BucketMode; +(function (BucketMode) { + /** + * @generated from enum value: BUCKET_MODE_UNSPECIFIED = 0; + */ + BucketMode[(BucketMode["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * @generated from enum value: BUCKET_MODE_CURATED = 1; + */ + BucketMode[(BucketMode["CURATED"] = 1)] = "CURATED"; + /** + * @generated from enum value: BUCKET_MODE_QUERY = 2; + */ + BucketMode[(BucketMode["QUERY"] = 2)] = "QUERY"; +})(BucketMode || (BucketMode = {})); +/** + * BucketsService manages on-demand buckets that query internal datasources + * + * @generated from service blockninja.v1.BucketsService + */ +const BucketsService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_buckets, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/buckets.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * List available internal datasources (from registry) + * + * @generated from rpc blockninja.v1.BucketsService.ListDatasources + */ +BucketsService.method.listDatasources; +/** + * Get a single datasource definition + * + * @generated from rpc blockninja.v1.BucketsService.GetDatasource + */ +BucketsService.method.getDatasource; +/** + * List buckets with optional filtering + * + * @generated from rpc blockninja.v1.BucketsService.ListBuckets + */ +BucketsService.method.listBuckets; +/** + * Get a single bucket + * + * @generated from rpc blockninja.v1.BucketsService.GetBucket + */ +BucketsService.method.getBucket; +/** + * Create a new bucket + * + * @generated from rpc blockninja.v1.BucketsService.CreateBucket + */ +BucketsService.method.createBucket; +/** + * Update a bucket + * + * @generated from rpc blockninja.v1.BucketsService.UpdateBucket + */ +BucketsService.method.updateBucket; +/** + * Delete a bucket + * + * @generated from rpc blockninja.v1.BucketsService.DeleteBucket + */ +BucketsService.method.deleteBucket; +/** + * Preview bucket data (for admin UI) + * + * @generated from rpc blockninja.v1.BucketsService.PreviewBucketData + */ +BucketsService.method.previewBucketData; +/** + * List items available for curated selection + * + * @generated from rpc blockninja.v1.BucketsService.ListCuratedItems + */ +BucketsService.method.listCuratedItems; +/** + * Get query schema for visual query builder + * + * @generated from rpc blockninja.v1.BucketsService.GetQuerySchema + */ +BucketsService.method.getQuerySchema; +/** + * AI-powered query generation from natural language + * + * @generated from rpc blockninja.v1.BucketsService.GenerateBucketQuery + */ +BucketsService.method.generateBucketQuery; +/** + * Invalidate cache for a bucket + * + * @generated from rpc blockninja.v1.BucketsService.InvalidateBucketCache + */ +BucketsService.method.invalidateBucketCache; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/cms_builder_service.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/cms_builder_service.proto. + */ +const file_blockninja_v1_cms_builder_service = /*@__PURE__*/ fileDesc( + "CidibG9ja25pbmphL3YxL2Ntc19idWlsZGVyX3NlcnZpY2UucHJvdG8SDWJsb2NrbmluamEudjEiXgoJR2l0UGx1Z2luEgwKBG5hbWUYASABKAkSEAoIcmVwb191cmwYAiABKAkSDgoGYnJhbmNoGAMgASgJEg8KB3NzaF9rZXkYBCABKAkSEAoIc3NoX3BvcnQYBSABKAUiUgoPQnVpbGRDTVNSZXF1ZXN0Ei0KC2dpdF9wbHVnaW5zGAEgAygLMhguYmxvY2tuaW5qYS52MS5HaXRQbHVnaW4SEAoIYnVpbGRfaWQYAiABKAkingEKDUJ1aWxkUHJvZ3Jlc3MSDAoEc3RlcBgBIAEoCRIPCgdtZXNzYWdlGAIgASgJEg4KBnN0cmVhbRgDIAEoCRIQCghpc19lcnJvchgEIAEoCBITCgtpc19jb21wbGV0ZRgFIAEoCBITCgtjb21taXRfaGFzaBgGIAEoCRIQCghidWlsdF9hdBgHIAEoCRIQCghidWlsZF9pZBgIIAEoCSIUChJQaW5nQnVpbGRlclJlcXVlc3QiNQoTUGluZ0J1aWxkZXJSZXNwb25zZRINCgVyZWFkeRgBIAEoCBIPCgd2ZXJzaW9uGAIgASgJMq4BChFDTVNCdWlsZGVyU2VydmljZRJKCghCdWlsZENNUxIeLmJsb2NrbmluamEudjEuQnVpbGRDTVNSZXF1ZXN0GhwuYmxvY2tuaW5qYS52MS5CdWlsZFByb2dyZXNzMAESTQoEUGluZxIhLmJsb2NrbmluamEudjEuUGluZ0J1aWxkZXJSZXF1ZXN0GiIuYmxvY2tuaW5qYS52MS5QaW5nQnVpbGRlclJlc3BvbnNlQswBChFjb20uYmxvY2tuaW5qYS52MUIWQ21zQnVpbGRlclNlcnZpY2VQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_google_protobuf_timestamp], +); +/** + * CMSBuilderService runs inside the persistent builder container. + * The CMS calls it over HTTP (Connect RPC) to compile CMS binaries with plugins. + * + * @generated from service blockninja.v1.CMSBuilderService + */ +const CMSBuilderService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_cms_builder_service, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/cms_builder_service.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Ping is a health/readiness check. + * + * @generated from rpc blockninja.v1.CMSBuilderService.Ping + */ +CMSBuilderService.method.ping; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/community_reviews.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/community_reviews.proto. + */ +const file_blockninja_v1_community_reviews = /*@__PURE__*/ fileDesc( + "CiVibG9ja25pbmphL3YxL2NvbW11bml0eV9yZXZpZXdzLnByb3RvEg1ibG9ja25pbmphLnYxIpcFCg9Db21tdW5pdHlSZXZpZXcSCgoCaWQYASABKAkSEAoIdGFibGVfaWQYAiABKAkSDgoGcm93X2lkGAMgASgJEg8KB3VzZXJfaWQYBCABKAkSDgoGc3RhdHVzGAUgASgJEhYKDm92ZXJhbGxfcmF0aW5nGAYgASgFEhgKC3Jldmlld190ZXh0GAcgASgJSACIAQESKAoHcmF0aW5ncxgIIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSDgoGcGhvdG9zGAkgAygJEhYKCXZpZGVvX3VybBgKIAEoCUgBiAEBEhUKDWhlbHBmdWxfY291bnQYCyABKAUSHQoQcmVqZWN0aW9uX3JlYXNvbhgMIAEoCUgCiAEBEi4KCmNyZWF0ZWRfYXQYDSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYDiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEiAKE2F1dGhvcl9kaXNwbGF5X25hbWUYDyABKAlIA4gBARIeChFhdXRob3JfYXZhdGFyX3VybBgQIAEoCUgEiAEBEhwKD2F1dGhvcl91c2VybmFtZRgRIAEoCUgFiAEBEhgKEHZpZXdlcl9oYXNfdm90ZWQYEiABKAgSEwoLaXNfZW5kb3JzZWQYEyABKAgSFwoPZWRpdGVkX2J5X2FkbWluGBQgASgIQg4KDF9yZXZpZXdfdGV4dEIMCgpfdmlkZW9fdXJsQhMKEV9yZWplY3Rpb25fcmVhc29uQhYKFF9hdXRob3JfZGlzcGxheV9uYW1lQhQKEl9hdXRob3JfYXZhdGFyX3VybEISChBfYXV0aG9yX3VzZXJuYW1lIj8KD1JhdGluZ0FnZ3JlZ2F0ZRIWCg5hdmVyYWdlX3JhdGluZxgBIAEoARIUCgxyZXZpZXdfY291bnQYAiABKAMi2QEKE1N1Ym1pdFJldmlld1JlcXVlc3QSEAoIdGFibGVfaWQYASABKAkSDgoGcm93X2lkGAIgASgJEhYKDm92ZXJhbGxfcmF0aW5nGAMgASgFEhgKC3Jldmlld190ZXh0GAQgASgJSACIAQESKAoHcmF0aW5ncxgFIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSDgoGcGhvdG9zGAYgAygJEhYKCXZpZGVvX3VybBgHIAEoCUgBiAEBQg4KDF9yZXZpZXdfdGV4dEIMCgpfdmlkZW9fdXJsIkYKFFN1Ym1pdFJldmlld1Jlc3BvbnNlEi4KBnJldmlldxgBIAEoCzIeLmJsb2NrbmluamEudjEuQ29tbXVuaXR5UmV2aWV3Ih4KEEdldFJldmlld1JlcXVlc3QSCgoCaWQYASABKAkiQwoRR2V0UmV2aWV3UmVzcG9uc2USLgoGcmV2aWV3GAEgASgLMh4uYmxvY2tuaW5qYS52MS5Db21tdW5pdHlSZXZpZXcijgEKGUxpc3RSZXZpZXdzRm9ySXRlbVJlcXVlc3QSEAoIdGFibGVfaWQYASABKAkSDgoGcm93X2lkGAIgASgJEi0KCnBhZ2luYXRpb24YAyABKAsyGS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb24SFAoHc29ydF9ieRgEIAEoCUgAiAEBQgoKCF9zb3J0X2J5IrcBChpMaXN0UmV2aWV3c0Zvckl0ZW1SZXNwb25zZRIvCgdyZXZpZXdzGAEgAygLMh4uYmxvY2tuaW5qYS52MS5Db21tdW5pdHlSZXZpZXcSNQoKcGFnaW5hdGlvbhgCIAEoCzIhLmJsb2NrbmluamEudjEuUGFnaW5hdGlvblJlc3BvbnNlEjEKCWFnZ3JlZ2F0ZRgDIAEoCzIeLmJsb2NrbmluamEudjEuUmF0aW5nQWdncmVnYXRlIloKGExpc3RSZXZpZXdzQnlVc2VyUmVxdWVzdBIPCgd1c2VyX2lkGAEgASgJEi0KCnBhZ2luYXRpb24YAiABKAsyGS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb24igwEKGUxpc3RSZXZpZXdzQnlVc2VyUmVzcG9uc2USLwoHcmV2aWV3cxgBIAMoCzIeLmJsb2NrbmluamEudjEuQ29tbXVuaXR5UmV2aWV3EjUKCnBhZ2luYXRpb24YAiABKAsyIS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb25SZXNwb25zZSInChJWb3RlSGVscGZ1bFJlcXVlc3QSEQoJcmV2aWV3X2lkGAEgASgJIiwKE1ZvdGVIZWxwZnVsUmVzcG9uc2USFQoNaGVscGZ1bF9jb3VudBgBIAEoBSImChFSZW1vdmVWb3RlUmVxdWVzdBIRCglyZXZpZXdfaWQYASABKAkiKwoSUmVtb3ZlVm90ZVJlc3BvbnNlEhUKDWhlbHBmdWxfY291bnQYASABKAUyvgQKF0NvbW11bml0eVJldmlld3NTZXJ2aWNlElcKDFN1Ym1pdFJldmlldxIiLmJsb2NrbmluamEudjEuU3VibWl0UmV2aWV3UmVxdWVzdBojLmJsb2NrbmluamEudjEuU3VibWl0UmV2aWV3UmVzcG9uc2USTgoJR2V0UmV2aWV3Eh8uYmxvY2tuaW5qYS52MS5HZXRSZXZpZXdSZXF1ZXN0GiAuYmxvY2tuaW5qYS52MS5HZXRSZXZpZXdSZXNwb25zZRJpChJMaXN0UmV2aWV3c0Zvckl0ZW0SKC5ibG9ja25pbmphLnYxLkxpc3RSZXZpZXdzRm9ySXRlbVJlcXVlc3QaKS5ibG9ja25pbmphLnYxLkxpc3RSZXZpZXdzRm9ySXRlbVJlc3BvbnNlEmYKEUxpc3RSZXZpZXdzQnlVc2VyEicuYmxvY2tuaW5qYS52MS5MaXN0UmV2aWV3c0J5VXNlclJlcXVlc3QaKC5ibG9ja25pbmphLnYxLkxpc3RSZXZpZXdzQnlVc2VyUmVzcG9uc2USVAoLVm90ZUhlbHBmdWwSIS5ibG9ja25pbmphLnYxLlZvdGVIZWxwZnVsUmVxdWVzdBoiLmJsb2NrbmluamEudjEuVm90ZUhlbHBmdWxSZXNwb25zZRJRCgpSZW1vdmVWb3RlEiAuYmxvY2tuaW5qYS52MS5SZW1vdmVWb3RlUmVxdWVzdBohLmJsb2NrbmluamEudjEuUmVtb3ZlVm90ZVJlc3BvbnNlQssBChFjb20uYmxvY2tuaW5qYS52MUIVQ29tbXVuaXR5UmV2aWV3c1Byb3RvUAFaSmdpdC5kZXYuYWxleGR1bm1vdy5jb20vYmxvY2svbmluamEvaW50ZXJuYWwvYXBpL2Jsb2NrbmluamEvdjE7YmxvY2tuaW5qYXYxogIDQlhYqgINQmxvY2tuaW5qYS5WMcoCDUJsb2NrbmluamFcVjHiAhlCbG9ja25pbmphXFYxXEdQQk1ldGFkYXRh6gIOQmxvY2tuaW5qYTo6VjFiBnByb3RvMw", + [file_blockninja_v1_common, file_google_protobuf_struct, file_google_protobuf_timestamp], +); +/** + * @generated from service blockninja.v1.CommunityReviewsService + */ +const CommunityReviewsService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_community_reviews, 0); + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/community_moderation.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/community_moderation.proto. + */ +const file_blockninja_v1_community_moderation = /*@__PURE__*/ fileDesc( + "CihibG9ja25pbmphL3YxL2NvbW11bml0eV9tb2RlcmF0aW9uLnByb3RvEg1ibG9ja25pbmphLnYxIm4KGUxpc3RQZW5kaW5nUmV2aWV3c1JlcXVlc3QSLQoKcGFnaW5hdGlvbhgBIAEoCzIZLmJsb2NrbmluamEudjEuUGFnaW5hdGlvbhIVCgh0YWJsZV9pZBgCIAEoCUgAiAEBQgsKCV90YWJsZV9pZCKEAQoaTGlzdFBlbmRpbmdSZXZpZXdzUmVzcG9uc2USLwoHcmV2aWV3cxgBIAMoCzIeLmJsb2NrbmluamEudjEuQ29tbXVuaXR5UmV2aWV3EjUKCnBhZ2luYXRpb24YAiABKAsyIS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb25SZXNwb25zZSIiChRBcHByb3ZlUmV2aWV3UmVxdWVzdBIKCgJpZBgBIAEoCSJHChVBcHByb3ZlUmV2aWV3UmVzcG9uc2USLgoGcmV2aWV3GAEgASgLMh4uYmxvY2tuaW5qYS52MS5Db21tdW5pdHlSZXZpZXciQQoTUmVqZWN0UmV2aWV3UmVxdWVzdBIKCgJpZBgBIAEoCRITCgZyZWFzb24YAiABKAlIAIgBAUIJCgdfcmVhc29uIkYKFFJlamVjdFJldmlld1Jlc3BvbnNlEi4KBnJldmlldxgBIAEoCzIeLmJsb2NrbmluamEudjEuQ29tbXVuaXR5UmV2aWV3IiEKE0RlbGV0ZVJldmlld1JlcXVlc3QSCgoCaWQYASABKAkiFgoURGVsZXRlUmV2aWV3UmVzcG9uc2UiQAoaQ291bnRQZW5kaW5nUmV2aWV3c1JlcXVlc3QSFQoIdGFibGVfaWQYASABKAlIAIgBAUILCglfdGFibGVfaWQiLAobQ291bnRQZW5kaW5nUmV2aWV3c1Jlc3BvbnNlEg0KBWNvdW50GAEgASgDMoMEChpDb21tdW5pdHlNb2RlcmF0aW9uU2VydmljZRJpChJMaXN0UGVuZGluZ1Jldmlld3MSKC5ibG9ja25pbmphLnYxLkxpc3RQZW5kaW5nUmV2aWV3c1JlcXVlc3QaKS5ibG9ja25pbmphLnYxLkxpc3RQZW5kaW5nUmV2aWV3c1Jlc3BvbnNlEloKDUFwcHJvdmVSZXZpZXcSIy5ibG9ja25pbmphLnYxLkFwcHJvdmVSZXZpZXdSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5BcHByb3ZlUmV2aWV3UmVzcG9uc2USVwoMUmVqZWN0UmV2aWV3EiIuYmxvY2tuaW5qYS52MS5SZWplY3RSZXZpZXdSZXF1ZXN0GiMuYmxvY2tuaW5qYS52MS5SZWplY3RSZXZpZXdSZXNwb25zZRJXCgxEZWxldGVSZXZpZXcSIi5ibG9ja25pbmphLnYxLkRlbGV0ZVJldmlld1JlcXVlc3QaIy5ibG9ja25pbmphLnYxLkRlbGV0ZVJldmlld1Jlc3BvbnNlEmwKE0NvdW50UGVuZGluZ1Jldmlld3MSKS5ibG9ja25pbmphLnYxLkNvdW50UGVuZGluZ1Jldmlld3NSZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5Db3VudFBlbmRpbmdSZXZpZXdzUmVzcG9uc2VCzgEKEWNvbS5ibG9ja25pbmphLnYxQhhDb21tdW5pdHlNb2RlcmF0aW9uUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_blockninja_v1_common, file_blockninja_v1_community_reviews, file_google_protobuf_timestamp], +); +/** + * CommunityModerationService provides admin moderation of community reviews + * + * @generated from service blockninja.v1.CommunityModerationService + */ +const CommunityModerationService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_community_moderation, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/community_moderation.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * @generated from rpc blockninja.v1.CommunityModerationService.ListPendingReviews + */ +CommunityModerationService.method.listPendingReviews; +/** + * @generated from rpc blockninja.v1.CommunityModerationService.ApproveReview + */ +CommunityModerationService.method.approveReview; +/** + * @generated from rpc blockninja.v1.CommunityModerationService.RejectReview + */ +CommunityModerationService.method.rejectReview; +/** + * @generated from rpc blockninja.v1.CommunityModerationService.DeleteReview + */ +CommunityModerationService.method.deleteReview; +/** + * @generated from rpc blockninja.v1.CommunityModerationService.CountPendingReviews + */ +CommunityModerationService.method.countPendingReviews; + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/community_reviews.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * @generated from rpc blockninja.v1.CommunityReviewsService.SubmitReview + */ +CommunityReviewsService.method.submitReview; +/** + * @generated from rpc blockninja.v1.CommunityReviewsService.GetReview + */ +CommunityReviewsService.method.getReview; +/** + * @generated from rpc blockninja.v1.CommunityReviewsService.ListReviewsForItem + */ +CommunityReviewsService.method.listReviewsForItem; +/** + * @generated from rpc blockninja.v1.CommunityReviewsService.ListReviewsByUser + */ +CommunityReviewsService.method.listReviewsByUser; +/** + * @generated from rpc blockninja.v1.CommunityReviewsService.VoteHelpful + */ +CommunityReviewsService.method.voteHelpful; +/** + * @generated from rpc blockninja.v1.CommunityReviewsService.RemoveVote + */ +CommunityReviewsService.method.removeVote; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/content_search.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/content_search.proto. + */ +const file_blockninja_v1_content_search = /*@__PURE__*/ fileDesc( + "CiJibG9ja25pbmphL3YxL2NvbnRlbnRfc2VhcmNoLnByb3RvEg1ibG9ja25pbmphLnYxIl4KFENvbnRlbnRTZWFyY2hSZXF1ZXN0Eg0KBXF1ZXJ5GAEgASgJEhgKC3NvdXJjZV90eXBlGAIgASgJSACIAQESDQoFbGltaXQYAyABKAVCDgoMX3NvdXJjZV90eXBlIlsKFUNvbnRlbnRTZWFyY2hSZXNwb25zZRIzCgdyZXN1bHRzGAEgAygLMiIuYmxvY2tuaW5qYS52MS5Db250ZW50U2VhcmNoUmVzdWx0Eg0KBXRvdGFsGAIgASgFIpcBChNDb250ZW50U2VhcmNoUmVzdWx0EhMKC3NvdXJjZV90eXBlGAEgASgJEhEKCXNvdXJjZV9pZBgCIAEoCRISCgpzaW1pbGFyaXR5GAMgASgBEhQKDGNvbnRlbnRfaGFzaBgEIAEoCRIuCgp1cGRhdGVkX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCJTChhHZW5lcmF0ZUVtYmVkZGluZ1JlcXVlc3QSEwoLc291cmNlX3R5cGUYASABKAkSEQoJc291cmNlX2lkGAIgASgJEg8KB2NvbnRlbnQYAyABKAkiQgoZR2VuZXJhdGVFbWJlZGRpbmdSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEhQKDGNvbnRlbnRfaGFzaBgCIAEoCSJlCh1CdWxrR2VuZXJhdGVFbWJlZGRpbmdzUmVxdWVzdBITCgtzb3VyY2VfdHlwZRgBIAEoCRIvCgVpdGVtcxgCIAMoCzIgLmJsb2NrbmluamEudjEuQnVsa0VtYmVkZGluZ0l0ZW0iNwoRQnVsa0VtYmVkZGluZ0l0ZW0SEQoJc291cmNlX2lkGAEgASgJEg8KB2NvbnRlbnQYAiABKAkilgEKHkJ1bGtHZW5lcmF0ZUVtYmVkZGluZ3NSZXNwb25zZRIVCg1zdWNjZXNzX2NvdW50GAEgASgFEhMKC2Vycm9yX2NvdW50GAIgASgFEhUKDXNraXBwZWRfY291bnQYAyABKAUSMQoGZXJyb3JzGAQgAygLMiEuYmxvY2tuaW5qYS52MS5CdWxrRW1iZWRkaW5nRXJyb3IiNgoSQnVsa0VtYmVkZGluZ0Vycm9yEhEKCXNvdXJjZV9pZBgBIAEoCRINCgVlcnJvchgCIAEoCSIbChlHZXRFbWJlZGRpbmdTdGF0dXNSZXF1ZXN0IoEBChpHZXRFbWJlZGRpbmdTdGF0dXNSZXNwb25zZRIYChB0b3RhbF9lbWJlZGRpbmdzGAEgASgDEhwKFGRhdGFfdGFibGVfcm93X2NvdW50GAIgASgDEhcKD2Jsb2dfcG9zdF9jb3VudBgDIAEoAxISCgpwYWdlX2NvdW50GAQgASgDIh0KG0xpc3RFbWJlZGRpbmdTb3VyY2VzUmVxdWVzdCK2AQoPRW1iZWRkaW5nU291cmNlEhMKC3NvdXJjZV90eXBlGAEgASgJEgwKBG5hbWUYAiABKAkSEQoJdGFibGVfa2V5GAMgASgJEg8KB2VuYWJsZWQYBCABKAgSEwoLdG90YWxfaXRlbXMYBSABKAMSFQoNaW5kZXhlZF9pdGVtcxgGIAEoAxIwCgxsYXN0X2luZGV4ZWQYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIk8KHExpc3RFbWJlZGRpbmdTb3VyY2VzUmVzcG9uc2USLwoHc291cmNlcxgBIAMoCzIeLmJsb2NrbmluamEudjEuRW1iZWRkaW5nU291cmNlIiMKIUdldERldGFpbGVkRW1iZWRkaW5nU3RhdHVzUmVxdWVzdCK2AQoQU291cmNlVHlwZVN0YXR1cxITCgtzb3VyY2VfdHlwZRgBIAEoCRIQCghtb2RlbF9pZBgCIAEoCRINCgVjb3VudBgDIAEoAxIUCgxjb3ZlcmFnZV9wY3QYBCABKAESKgoGb2xkZXN0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIqCgZuZXdlc3QYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIqMBCiJHZXREZXRhaWxlZEVtYmVkZGluZ1N0YXR1c1Jlc3BvbnNlEhgKEHRvdGFsX2VtYmVkZGluZ3MYASABKAMSNQoMc291cmNlX3R5cGVzGAIgAygLMh8uYmxvY2tuaW5qYS52MS5Tb3VyY2VUeXBlU3RhdHVzEhoKEnByb3ZpZGVyX2F2YWlsYWJsZRgDIAEoCBIQCghtb2RlbF9pZBgEIAEoCSIZChdMaXN0U2VhcmNoU2NvcGVzUmVxdWVzdCKBAQoLU2VhcmNoU2NvcGUSCwoDa2V5GAEgASgJEgwKBG5hbWUYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSFAoMc291cmNlX3R5cGVzGAQgAygJEhgKEHRhYmxlX2tleV9maWx0ZXIYBSABKAkSEgoKaXNfZGVmYXVsdBgGIAEoCCJGChhMaXN0U2VhcmNoU2NvcGVzUmVzcG9uc2USKgoGc2NvcGVzGAEgAygLMhouYmxvY2tuaW5qYS52MS5TZWFyY2hTY29wZSKOAQoYQ3JlYXRlU2VhcmNoU2NvcGVSZXF1ZXN0EgsKA2tleRgBIAEoCRIMCgRuYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEhQKDHNvdXJjZV90eXBlcxgEIAMoCRIYChB0YWJsZV9rZXlfZmlsdGVyGAUgASgJEhIKCmlzX2RlZmF1bHQYBiABKAgiRgoZQ3JlYXRlU2VhcmNoU2NvcGVSZXNwb25zZRIpCgVzY29wZRgBIAEoCzIaLmJsb2NrbmluamEudjEuU2VhcmNoU2NvcGUijgEKGFVwZGF0ZVNlYXJjaFNjb3BlUmVxdWVzdBILCgNrZXkYASABKAkSDAoEbmFtZRgCIAEoCRITCgtkZXNjcmlwdGlvbhgDIAEoCRIUCgxzb3VyY2VfdHlwZXMYBCADKAkSGAoQdGFibGVfa2V5X2ZpbHRlchgFIAEoCRISCgppc19kZWZhdWx0GAYgASgIIkYKGVVwZGF0ZVNlYXJjaFNjb3BlUmVzcG9uc2USKQoFc2NvcGUYASABKAsyGi5ibG9ja25pbmphLnYxLlNlYXJjaFNjb3BlIicKGERlbGV0ZVNlYXJjaFNjb3BlUmVxdWVzdBILCgNrZXkYASABKAkiLAoZRGVsZXRlU2VhcmNoU2NvcGVSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIIisKFFJlaW5kZXhTb3VyY2VSZXF1ZXN0EhMKC3NvdXJjZV90eXBlGAEgASgJIjgKFVJlaW5kZXhTb3VyY2VSZXNwb25zZRIOCgZzdGF0dXMYASABKAkSDwoHbWVzc2FnZRgCIAEoCSIzChxQdXJnZVNvdXJjZUVtYmVkZGluZ3NSZXF1ZXN0EhMKC3NvdXJjZV90eXBlGAEgASgJIjYKHVB1cmdlU291cmNlRW1iZWRkaW5nc1Jlc3BvbnNlEhUKDWRlbGV0ZWRfY291bnQYASABKAMiHQobUmVpbmRleEFsbEVtYmVkZGluZ3NSZXF1ZXN0Ik0KHFJlaW5kZXhBbGxFbWJlZGRpbmdzUmVzcG9uc2USDgoGc3RhdHVzGAEgASgJEg4KBnJlYXNvbhgCIAEoCRINCgVtb2RlbBgDIAEoCSIjCiFHZXRFbWJlZGRpbmdQcm92aWRlclN0YXR1c1JlcXVlc3QimgEKIkdldEVtYmVkZGluZ1Byb3ZpZGVyU3RhdHVzUmVzcG9uc2USGgoScHJvdmlkZXJfYXZhaWxhYmxlGAEgASgIEg0KBW1vZGVsGAIgASgJEg0KBXRvdGFsGAMgASgDEhcKD2RhdGFfdGFibGVfcm93cxgEIAEoAxISCgpibG9nX3Bvc3RzGAUgASgDEg0KBXBhZ2VzGAYgASgDMowMChRDb250ZW50U2VhcmNoU2VydmljZRJTCgZTZWFyY2gSIy5ibG9ja25pbmphLnYxLkNvbnRlbnRTZWFyY2hSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5Db250ZW50U2VhcmNoUmVzcG9uc2USZgoRR2VuZXJhdGVFbWJlZGRpbmcSJy5ibG9ja25pbmphLnYxLkdlbmVyYXRlRW1iZWRkaW5nUmVxdWVzdBooLmJsb2NrbmluamEudjEuR2VuZXJhdGVFbWJlZGRpbmdSZXNwb25zZRJ1ChZCdWxrR2VuZXJhdGVFbWJlZGRpbmdzEiwuYmxvY2tuaW5qYS52MS5CdWxrR2VuZXJhdGVFbWJlZGRpbmdzUmVxdWVzdBotLmJsb2NrbmluamEudjEuQnVsa0dlbmVyYXRlRW1iZWRkaW5nc1Jlc3BvbnNlEmkKEkdldEVtYmVkZGluZ1N0YXR1cxIoLmJsb2NrbmluamEudjEuR2V0RW1iZWRkaW5nU3RhdHVzUmVxdWVzdBopLmJsb2NrbmluamEudjEuR2V0RW1iZWRkaW5nU3RhdHVzUmVzcG9uc2USbwoUTGlzdEVtYmVkZGluZ1NvdXJjZXMSKi5ibG9ja25pbmphLnYxLkxpc3RFbWJlZGRpbmdTb3VyY2VzUmVxdWVzdBorLmJsb2NrbmluamEudjEuTGlzdEVtYmVkZGluZ1NvdXJjZXNSZXNwb25zZRKBAQoaR2V0RGV0YWlsZWRFbWJlZGRpbmdTdGF0dXMSMC5ibG9ja25pbmphLnYxLkdldERldGFpbGVkRW1iZWRkaW5nU3RhdHVzUmVxdWVzdBoxLmJsb2NrbmluamEudjEuR2V0RGV0YWlsZWRFbWJlZGRpbmdTdGF0dXNSZXNwb25zZRJjChBMaXN0U2VhcmNoU2NvcGVzEiYuYmxvY2tuaW5qYS52MS5MaXN0U2VhcmNoU2NvcGVzUmVxdWVzdBonLmJsb2NrbmluamEudjEuTGlzdFNlYXJjaFNjb3Blc1Jlc3BvbnNlEmYKEUNyZWF0ZVNlYXJjaFNjb3BlEicuYmxvY2tuaW5qYS52MS5DcmVhdGVTZWFyY2hTY29wZVJlcXVlc3QaKC5ibG9ja25pbmphLnYxLkNyZWF0ZVNlYXJjaFNjb3BlUmVzcG9uc2USZgoRVXBkYXRlU2VhcmNoU2NvcGUSJy5ibG9ja25pbmphLnYxLlVwZGF0ZVNlYXJjaFNjb3BlUmVxdWVzdBooLmJsb2NrbmluamEudjEuVXBkYXRlU2VhcmNoU2NvcGVSZXNwb25zZRJmChFEZWxldGVTZWFyY2hTY29wZRInLmJsb2NrbmluamEudjEuRGVsZXRlU2VhcmNoU2NvcGVSZXF1ZXN0GiguYmxvY2tuaW5qYS52MS5EZWxldGVTZWFyY2hTY29wZVJlc3BvbnNlEloKDVJlaW5kZXhTb3VyY2USIy5ibG9ja25pbmphLnYxLlJlaW5kZXhTb3VyY2VSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5SZWluZGV4U291cmNlUmVzcG9uc2UScgoVUHVyZ2VTb3VyY2VFbWJlZGRpbmdzEisuYmxvY2tuaW5qYS52MS5QdXJnZVNvdXJjZUVtYmVkZGluZ3NSZXF1ZXN0GiwuYmxvY2tuaW5qYS52MS5QdXJnZVNvdXJjZUVtYmVkZGluZ3NSZXNwb25zZRJvChRSZWluZGV4QWxsRW1iZWRkaW5ncxIqLmJsb2NrbmluamEudjEuUmVpbmRleEFsbEVtYmVkZGluZ3NSZXF1ZXN0GisuYmxvY2tuaW5qYS52MS5SZWluZGV4QWxsRW1iZWRkaW5nc1Jlc3BvbnNlEoEBChpHZXRFbWJlZGRpbmdQcm92aWRlclN0YXR1cxIwLmJsb2NrbmluamEudjEuR2V0RW1iZWRkaW5nUHJvdmlkZXJTdGF0dXNSZXF1ZXN0GjEuYmxvY2tuaW5qYS52MS5HZXRFbWJlZGRpbmdQcm92aWRlclN0YXR1c1Jlc3BvbnNlQsgBChFjb20uYmxvY2tuaW5qYS52MUISQ29udGVudFNlYXJjaFByb3RvUAFaSmdpdC5kZXYuYWxleGR1bm1vdy5jb20vYmxvY2svbmluamEvaW50ZXJuYWwvYXBpL2Jsb2NrbmluamEvdjE7YmxvY2tuaW5qYXYxogIDQlhYqgINQmxvY2tuaW5qYS5WMcoCDUJsb2NrbmluamFcVjHiAhlCbG9ja25pbmphXFYxXEdQQk1ldGFkYXRh6gIOQmxvY2tuaW5qYTo6VjFiBnByb3RvMw", + [file_google_protobuf_timestamp], +); +/** + * ContentSearchService provides semantic search over CMS content using pgvector embeddings + * + * @generated from service blockninja.v1.ContentSearchService + */ +const ContentSearchService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_content_search, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/content_search.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Search content using natural language query + * + * @generated from rpc blockninja.v1.ContentSearchService.Search + */ +ContentSearchService.method.search; +/** + * Generate embedding for a single piece of content + * + * @generated from rpc blockninja.v1.ContentSearchService.GenerateEmbedding + */ +ContentSearchService.method.generateEmbedding; +/** + * Bulk generate embeddings for multiple content items + * + * @generated from rpc blockninja.v1.ContentSearchService.BulkGenerateEmbeddings + */ +ContentSearchService.method.bulkGenerateEmbeddings; +/** + * Get embedding coverage status across content types + * + * @generated from rpc blockninja.v1.ContentSearchService.GetEmbeddingStatus + */ +ContentSearchService.method.getEmbeddingStatus; +/** + * List all indexable data sources with enabled/disabled status + * + * @generated from rpc blockninja.v1.ContentSearchService.ListEmbeddingSources + */ +ContentSearchService.method.listEmbeddingSources; +/** + * Detailed per-source-type embedding status (counts, coverage, timestamps) + * + * @generated from rpc blockninja.v1.ContentSearchService.GetDetailedEmbeddingStatus + */ +ContentSearchService.method.getDetailedEmbeddingStatus; +/** + * List configured search scopes + * + * @generated from rpc blockninja.v1.ContentSearchService.ListSearchScopes + */ +ContentSearchService.method.listSearchScopes; +/** + * Create a named search scope + * + * @generated from rpc blockninja.v1.ContentSearchService.CreateSearchScope + */ +ContentSearchService.method.createSearchScope; +/** + * Update a search scope + * + * @generated from rpc blockninja.v1.ContentSearchService.UpdateSearchScope + */ +ContentSearchService.method.updateSearchScope; +/** + * Delete a search scope + * + * @generated from rpc blockninja.v1.ContentSearchService.DeleteSearchScope + */ +ContentSearchService.method.deleteSearchScope; +/** + * Trigger re-indexing for a specific source type + * + * @generated from rpc blockninja.v1.ContentSearchService.ReindexSource + */ +ContentSearchService.method.reindexSource; +/** + * Delete all embeddings for a source type + * + * @generated from rpc blockninja.v1.ContentSearchService.PurgeSourceEmbeddings + */ +ContentSearchService.method.purgeSourceEmbeddings; +/** + * Trigger full reindexing of all embedding sources + * + * @generated from rpc blockninja.v1.ContentSearchService.ReindexAllEmbeddings + */ +ContentSearchService.method.reindexAllEmbeddings; +/** + * Get embedding provider status and counts (lightweight admin check) + * + * @generated from rpc blockninja.v1.ContentSearchService.GetEmbeddingProviderStatus + */ +ContentSearchService.method.getEmbeddingProviderStatus; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/dashboard.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/dashboard.proto. + */ +const file_blockninja_v1_dashboard = /*@__PURE__*/ fileDesc( + "Ch1ibG9ja25pbmphL3YxL2Rhc2hib2FyZC5wcm90bxINYmxvY2tuaW5qYS52MSIbChlHZXREYXNoYm9hcmRBbGVydHNSZXF1ZXN0InEKFERhc2hib2FyZEFsZXJ0Q291bnRzEhIKCmVycm9yc180MDQYASABKAMSGwoTdW5wdWJsaXNoZWRfY2hhbmdlcxgCIAEoAxITCgttaXNzaW5nX3NlbxgDIAEoAxITCgtsb3dfdHJhZmZpYxgEIAEoAyJRChpHZXREYXNoYm9hcmRBbGVydHNSZXNwb25zZRIzCgZjb3VudHMYASABKAsyIy5ibG9ja25pbmphLnYxLkRhc2hib2FyZEFsZXJ0Q291bnRzIhgKFkdldENvbnRlbnRTdGF0c1JlcXVlc3QizwEKF0dldENvbnRlbnRTdGF0c1Jlc3BvbnNlEhcKD3B1Ymxpc2hlZF9wYWdlcxgBIAEoAxITCgtkcmFmdF9wYWdlcxgCIAEoAxIXCg9wdWJsaXNoZWRfcG9zdHMYAyABKAMSEwoLZHJhZnRfcG9zdHMYBCABKAMSEwoLdG90YWxfcGFnZXMYBSABKAMSEwoLdG90YWxfcG9zdHMYBiABKAMSGQoRdG90YWxfc3Vic2NyaWJlcnMYByABKAMSEwoLdG90YWxfbWVkaWEYCCABKAMiKQoYR2V0UmVjZW50QWN0aXZpdHlSZXF1ZXN0Eg0KBWxpbWl0GAEgASgFIq4BCgxBY3Rpdml0eUl0ZW0SCgoCaWQYASABKAkSEwoLYWN0b3JfZW1haWwYAiABKAkSDgoGYWN0aW9uGAMgASgJEhAKCHJlc291cmNlGAQgASgJEhMKC3Jlc291cmNlX2lkGAUgASgJEhYKDnJlc291cmNlX3RpdGxlGAYgASgJEi4KCmNyZWF0ZWRfYXQYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIkwKGUdldFJlY2VudEFjdGl2aXR5UmVzcG9uc2USLwoKYWN0aXZpdGllcxgBIAMoCzIbLmJsb2NrbmluamEudjEuQWN0aXZpdHlJdGVtIiQKE0dldDQwNEVycm9yc1JlcXVlc3QSDQoFbGltaXQYASABKAUiJgoIRXJyb3I0MDQSDAoEcGF0aBgBIAEoCRIMCgRoaXRzGAIgASgDIlQKFEdldDQwNEVycm9yc1Jlc3BvbnNlEicKBmVycm9ycxgBIAMoCzIXLmJsb2NrbmluamEudjEuRXJyb3I0MDQSEwoLdG90YWxfY291bnQYAiABKAMiPQoZR2V0TG93VHJhZmZpY1BhZ2VzUmVxdWVzdBIRCgltaW5fdmlld3MYASABKAUSDQoFbGltaXQYAiABKAUiSAoOTG93VHJhZmZpY1BhZ2USCgoCaWQYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEc2x1ZxgDIAEoCRINCgV2aWV3cxgEIAEoAyJfChpHZXRMb3dUcmFmZmljUGFnZXNSZXNwb25zZRIsCgVwYWdlcxgBIAMoCzIdLmJsb2NrbmluamEudjEuTG93VHJhZmZpY1BhZ2USEwoLdG90YWxfY291bnQYAiABKAMiNgolR2V0UGFnZXNXaXRoVW5wdWJsaXNoZWRDaGFuZ2VzUmVxdWVzdBINCgVsaW1pdBgBIAEoBSJ9Cg9VbnB1Ymxpc2hlZFBhZ2USCgoCaWQYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEc2x1ZxgDIAEoCRIRCglwb3N0X3R5cGUYBCABKAkSLgoKdXBkYXRlZF9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAibAomR2V0UGFnZXNXaXRoVW5wdWJsaXNoZWRDaGFuZ2VzUmVzcG9uc2USLQoFcGFnZXMYASADKAsyHi5ibG9ja25pbmphLnYxLlVucHVibGlzaGVkUGFnZRITCgt0b3RhbF9jb3VudBgCIAEoAyIqChlHZXRQYWdlc01pc3NpbmdTRU9SZXF1ZXN0Eg0KBWxpbWl0GAEgASgFIo4BCg5NaXNzaW5nU0VPUGFnZRIKCgJpZBgBIAEoCRINCgV0aXRsZRgCIAEoCRIMCgRzbHVnGAMgASgJEhEKCXBvc3RfdHlwZRgEIAEoCRIgChhtaXNzaW5nX21ldGFfZGVzY3JpcHRpb24YBSABKAgSHgoWbWlzc2luZ19mZWF0dXJlZF9pbWFnZRgGIAEoCCJfChpHZXRQYWdlc01pc3NpbmdTRU9SZXNwb25zZRIsCgVwYWdlcxgBIAMoCzIdLmJsb2NrbmluamEudjEuTWlzc2luZ1NFT1BhZ2USEwoLdG90YWxfY291bnQYAiABKAMyhgYKEERhc2hib2FyZFNlcnZpY2USaQoSR2V0RGFzaGJvYXJkQWxlcnRzEiguYmxvY2tuaW5qYS52MS5HZXREYXNoYm9hcmRBbGVydHNSZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5HZXREYXNoYm9hcmRBbGVydHNSZXNwb25zZRJgCg9HZXRDb250ZW50U3RhdHMSJS5ibG9ja25pbmphLnYxLkdldENvbnRlbnRTdGF0c1JlcXVlc3QaJi5ibG9ja25pbmphLnYxLkdldENvbnRlbnRTdGF0c1Jlc3BvbnNlEmYKEUdldFJlY2VudEFjdGl2aXR5EicuYmxvY2tuaW5qYS52MS5HZXRSZWNlbnRBY3Rpdml0eVJlcXVlc3QaKC5ibG9ja25pbmphLnYxLkdldFJlY2VudEFjdGl2aXR5UmVzcG9uc2USVwoMR2V0NDA0RXJyb3JzEiIuYmxvY2tuaW5qYS52MS5HZXQ0MDRFcnJvcnNSZXF1ZXN0GiMuYmxvY2tuaW5qYS52MS5HZXQ0MDRFcnJvcnNSZXNwb25zZRJpChJHZXRMb3dUcmFmZmljUGFnZXMSKC5ibG9ja25pbmphLnYxLkdldExvd1RyYWZmaWNQYWdlc1JlcXVlc3QaKS5ibG9ja25pbmphLnYxLkdldExvd1RyYWZmaWNQYWdlc1Jlc3BvbnNlEo0BCh5HZXRQYWdlc1dpdGhVbnB1Ymxpc2hlZENoYW5nZXMSNC5ibG9ja25pbmphLnYxLkdldFBhZ2VzV2l0aFVucHVibGlzaGVkQ2hhbmdlc1JlcXVlc3QaNS5ibG9ja25pbmphLnYxLkdldFBhZ2VzV2l0aFVucHVibGlzaGVkQ2hhbmdlc1Jlc3BvbnNlEmkKEkdldFBhZ2VzTWlzc2luZ1NFTxIoLmJsb2NrbmluamEudjEuR2V0UGFnZXNNaXNzaW5nU0VPUmVxdWVzdBopLmJsb2NrbmluamEudjEuR2V0UGFnZXNNaXNzaW5nU0VPUmVzcG9uc2VCxAEKEWNvbS5ibG9ja25pbmphLnYxQg5EYXNoYm9hcmRQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_google_protobuf_timestamp], +); +/** + * DashboardService provides aggregated data for the admin dashboard + * + * @generated from service blockninja.v1.DashboardService + */ +const DashboardService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_dashboard, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/dashboard.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Get all dashboard alerts in one call + * + * @generated from rpc blockninja.v1.DashboardService.GetDashboardAlerts + */ +DashboardService.method.getDashboardAlerts; +/** + * Get content statistics + * + * @generated from rpc blockninja.v1.DashboardService.GetContentStats + */ +DashboardService.method.getContentStats; +/** + * Get recent content activity + * + * @generated from rpc blockninja.v1.DashboardService.GetRecentActivity + */ +DashboardService.method.getRecentActivity; +/** + * Get 404 errors + * + * @generated from rpc blockninja.v1.DashboardService.Get404Errors + */ +DashboardService.method.get404Errors; +/** + * Get low traffic pages + * + * @generated from rpc blockninja.v1.DashboardService.GetLowTrafficPages + */ +DashboardService.method.getLowTrafficPages; +/** + * Get pages with unpublished changes + * + * @generated from rpc blockninja.v1.DashboardService.GetPagesWithUnpublishedChanges + */ +DashboardService.method.getPagesWithUnpublishedChanges; +/** + * Get pages missing SEO + * + * @generated from rpc blockninja.v1.DashboardService.GetPagesMissingSEO + */ +DashboardService.method.getPagesMissingSEO; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/data_suggestions.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/data_suggestions.proto. + */ +const file_blockninja_v1_data_suggestions = /*@__PURE__*/ fileDesc( + "CiRibG9ja25pbmphL3YxL2RhdGFfc3VnZ2VzdGlvbnMucHJvdG8SDWJsb2NrbmluamEudjEimgQKDkRhdGFTdWdnZXN0aW9uEgoKAmlkGAEgASgJEhAKCHRhYmxlX2lkGAIgASgJEg4KBnJvd19pZBgDIAEoCRIPCgd1c2VyX2lkGAQgASgJEjIKEXN1Z2dlc3RlZF9jaGFuZ2VzGAUgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIOCgZyZWFzb24YBiABKAkSDgoGc3RhdHVzGAcgASgJEhgKC3Jldmlld2VkX2J5GAggASgJSACIAQESNAoLcmV2aWV3ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSAGIAQESHQoQcmVqZWN0aW9uX3JlYXNvbhgKIAEoCUgCiAEBEi4KCmNyZWF0ZWRfYXQYCyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYDCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjIKDnJvd192ZXJzaW9uX2F0GA0gASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIZChF1c2VyX2Rpc3BsYXlfbmFtZRgOIAEoCRIQCghyb3dfbmFtZRgPIAEoCRIQCghpc19zdGFsZRgQIAEoCEIOCgxfcmV2aWV3ZWRfYnlCDgoMX3Jldmlld2VkX2F0QhMKEV9yZWplY3Rpb25fcmVhc29uIn8KF1N1Ym1pdFN1Z2dlc3Rpb25SZXF1ZXN0EhAKCHRhYmxlX2lkGAEgASgJEg4KBnJvd19pZBgCIAEoCRIyChFzdWdnZXN0ZWRfY2hhbmdlcxgDIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSDgoGcmVhc29uGAQgASgJImQKGFN1Ym1pdFN1Z2dlc3Rpb25SZXNwb25zZRIxCgpzdWdnZXN0aW9uGAEgASgLMh0uYmxvY2tuaW5qYS52MS5EYXRhU3VnZ2VzdGlvbhIVCg1hdXRvX2FwcHJvdmVkGAIgASgIImIKFkxpc3RTdWdnZXN0aW9uc1JlcXVlc3QSEAoIdGFibGVfaWQYASABKAkSFQoNc3RhdHVzX2ZpbHRlchgCIAEoCRIMCgRwYWdlGAMgASgFEhEKCXBhZ2Vfc2l6ZRgEIAEoBSJiChdMaXN0U3VnZ2VzdGlvbnNSZXNwb25zZRIyCgtzdWdnZXN0aW9ucxgBIAMoCzIdLmJsb2NrbmluamEudjEuRGF0YVN1Z2dlc3Rpb24SEwoLdG90YWxfY291bnQYAiABKAUiQAoYQXBwcm92ZVN1Z2dlc3Rpb25SZXF1ZXN0EhUKDXN1Z2dlc3Rpb25faWQYASABKAkSDQoFZm9yY2UYAiABKAgiYQoZQXBwcm92ZVN1Z2dlc3Rpb25SZXNwb25zZRIxCgpzdWdnZXN0aW9uGAEgASgLMh0uYmxvY2tuaW5qYS52MS5EYXRhU3VnZ2VzdGlvbhIRCgl3YXNfc3RhbGUYAiABKAgiQAoXUmVqZWN0U3VnZ2VzdGlvblJlcXVlc3QSFQoNc3VnZ2VzdGlvbl9pZBgBIAEoCRIOCgZyZWFzb24YAiABKAkiTQoYUmVqZWN0U3VnZ2VzdGlvblJlc3BvbnNlEjEKCnN1Z2dlc3Rpb24YASABKAsyHS5ibG9ja25pbmphLnYxLkRhdGFTdWdnZXN0aW9uIjsKGExpc3RNeVN1Z2dlc3Rpb25zUmVxdWVzdBIMCgRwYWdlGAEgASgFEhEKCXBhZ2Vfc2l6ZRgCIAEoBSJkChlMaXN0TXlTdWdnZXN0aW9uc1Jlc3BvbnNlEjIKC3N1Z2dlc3Rpb25zGAEgAygLMh0uYmxvY2tuaW5qYS52MS5EYXRhU3VnZ2VzdGlvbhITCgt0b3RhbF9jb3VudBgCIAEoBTKUBAoWRGF0YVN1Z2dlc3Rpb25zU2VydmljZRJjChBTdWJtaXRTdWdnZXN0aW9uEiYuYmxvY2tuaW5qYS52MS5TdWJtaXRTdWdnZXN0aW9uUmVxdWVzdBonLmJsb2NrbmluamEudjEuU3VibWl0U3VnZ2VzdGlvblJlc3BvbnNlEmAKD0xpc3RTdWdnZXN0aW9ucxIlLmJsb2NrbmluamEudjEuTGlzdFN1Z2dlc3Rpb25zUmVxdWVzdBomLmJsb2NrbmluamEudjEuTGlzdFN1Z2dlc3Rpb25zUmVzcG9uc2USZgoRQXBwcm92ZVN1Z2dlc3Rpb24SJy5ibG9ja25pbmphLnYxLkFwcHJvdmVTdWdnZXN0aW9uUmVxdWVzdBooLmJsb2NrbmluamEudjEuQXBwcm92ZVN1Z2dlc3Rpb25SZXNwb25zZRJjChBSZWplY3RTdWdnZXN0aW9uEiYuYmxvY2tuaW5qYS52MS5SZWplY3RTdWdnZXN0aW9uUmVxdWVzdBonLmJsb2NrbmluamEudjEuUmVqZWN0U3VnZ2VzdGlvblJlc3BvbnNlEmYKEUxpc3RNeVN1Z2dlc3Rpb25zEicuYmxvY2tuaW5qYS52MS5MaXN0TXlTdWdnZXN0aW9uc1JlcXVlc3QaKC5ibG9ja25pbmphLnYxLkxpc3RNeVN1Z2dlc3Rpb25zUmVzcG9uc2VCygEKEWNvbS5ibG9ja25pbmphLnYxQhREYXRhU3VnZ2VzdGlvbnNQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_google_protobuf_struct, file_google_protobuf_timestamp], +); +/** + * @generated from service blockninja.v1.DataSuggestionsService + */ +const DataSuggestionsService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_data_suggestions, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/data_suggestions.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Submit a suggestion for field corrections (public auth required) + * + * @generated from rpc blockninja.v1.DataSuggestionsService.SubmitSuggestion + */ +DataSuggestionsService.method.submitSuggestion; +/** + * List suggestions for a table (admin only) + * + * @generated from rpc blockninja.v1.DataSuggestionsService.ListSuggestions + */ +DataSuggestionsService.method.listSuggestions; +/** + * Approve a suggestion — applies changes to the row (admin only) + * + * @generated from rpc blockninja.v1.DataSuggestionsService.ApproveSuggestion + */ +DataSuggestionsService.method.approveSuggestion; +/** + * Reject a suggestion (admin only) + * + * @generated from rpc blockninja.v1.DataSuggestionsService.RejectSuggestion + */ +DataSuggestionsService.method.rejectSuggestion; +/** + * List current user's own suggestions (public auth required) + * + * @generated from rpc blockninja.v1.DataSuggestionsService.ListMySuggestions + */ +DataSuggestionsService.method.listMySuggestions; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/data_tables.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/data_tables.proto. + */ +const file_blockninja_v1_data_tables = /*@__PURE__*/ fileDesc( + "Ch9ibG9ja25pbmphL3YxL2RhdGFfdGFibGVzLnByb3RvEg1ibG9ja25pbmphLnYxIvECCglEYXRhVGFibGUSCgoCaWQYASABKAkSEQoJdGFibGVfa2V5GAIgASgJEgwKBG5hbWUYAyABKAkSEwoLZGVzY3JpcHRpb24YBCABKAkSJwoGc2NoZW1hGAUgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIZChFwcmltYXJ5X2tleV9maWVsZBgGIAEoCRIRCglyb3dfY291bnQYByABKAUSNQoRbGFzdF9pbmdlc3Rpb25fYXQYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhIKCmNyZWF0ZWRfYnkYCyABKAkSIAoYaW5nZXN0aW9uX3BpcGVsaW5lX2NvdW50GAwgASgFIskDCg9GaWVsZERlZmluaXRpb24SCwoDa2V5GAEgASgJEg0KBXRpdGxlGAIgASgJEiYKBHR5cGUYAyABKA4yGC5ibG9ja25pbmphLnYxLkZpZWxkVHlwZRIQCghyZXF1aXJlZBgEIAEoCBITCgtkZXNjcmlwdGlvbhgFIAEoCRIXCgptaW5fbGVuZ3RoGAYgASgFSACIAQESFwoKbWF4X2xlbmd0aBgHIAEoBUgBiAEBEg8KB3BhdHRlcm4YCCABKAkSFAoHbWluaW11bRgJIAEoAUgCiAEBEhQKB21heGltdW0YCiABKAFIA4gBARIsCgdvcHRpb25zGAsgAygLMhsuYmxvY2tuaW5qYS52MS5TZWxlY3RPcHRpb24SLQoNZGVmYXVsdF92YWx1ZRgMIAEoCzIWLmdvb2dsZS5wcm90b2J1Zi5WYWx1ZRITCgtwbGFjZWhvbGRlchgNIAEoCRIRCgloZWxwX3RleHQYDiABKAkSDQoFd2lkdGgYDyABKAkSEgoKc29ydF9vcmRlchgQIAEoBUINCgtfbWluX2xlbmd0aEINCgtfbWF4X2xlbmd0aEIKCghfbWluaW11bUIKCghfbWF4aW11bSIsCgxTZWxlY3RPcHRpb24SDQoFdmFsdWUYASABKAkSDQoFbGFiZWwYAiABKAkiRgoVTGlzdERhdGFUYWJsZXNSZXF1ZXN0Eg0KBWxpbWl0GAEgASgFEg4KBm9mZnNldBgCIAEoBRIOCgZzZWFyY2gYAyABKAkiUQoWTGlzdERhdGFUYWJsZXNSZXNwb25zZRIoCgZ0YWJsZXMYASADKAsyGC5ibG9ja25pbmphLnYxLkRhdGFUYWJsZRINCgV0b3RhbBgCIAEoBSIhChNHZXREYXRhVGFibGVSZXF1ZXN0EgoKAmlkGAEgASgJIj8KFEdldERhdGFUYWJsZVJlc3BvbnNlEicKBXRhYmxlGAEgASgLMhguYmxvY2tuaW5qYS52MS5EYXRhVGFibGUikgEKFkNyZWF0ZURhdGFUYWJsZVJlcXVlc3QSEQoJdGFibGVfa2V5GAEgASgJEgwKBG5hbWUYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSJwoGc2NoZW1hGAQgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIZChFwcmltYXJ5X2tleV9maWVsZBgFIAEoCSJCChdDcmVhdGVEYXRhVGFibGVSZXNwb25zZRInCgV0YWJsZRgBIAEoCzIYLmJsb2NrbmluamEudjEuRGF0YVRhYmxlIskBChZVcGRhdGVEYXRhVGFibGVSZXF1ZXN0EgoKAmlkGAEgASgJEhEKBG5hbWUYAiABKAlIAIgBARIYCgtkZXNjcmlwdGlvbhgDIAEoCUgBiAEBEicKBnNjaGVtYRgEIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSHgoRcHJpbWFyeV9rZXlfZmllbGQYBSABKAlIAogBAUIHCgVfbmFtZUIOCgxfZGVzY3JpcHRpb25CFAoSX3ByaW1hcnlfa2V5X2ZpZWxkIkIKF1VwZGF0ZURhdGFUYWJsZVJlc3BvbnNlEicKBXRhYmxlGAEgASgLMhguYmxvY2tuaW5qYS52MS5EYXRhVGFibGUiJAoWRGVsZXRlRGF0YVRhYmxlUmVxdWVzdBIKCgJpZBgBIAEoCSIZChdEZWxldGVEYXRhVGFibGVSZXNwb25zZSJ+ChpJbmZlclNjaGVtYUZyb21EYXRhUmVxdWVzdBISCghjc3ZfZGF0YRgBIAEoDEgAEhMKCWpzb25fZGF0YRgCIAEoDEgAEhYKDmNzdl9oYXNfaGVhZGVyGAMgASgIEhUKDWNzdl9kZWxpbWl0ZXIYBCABKAlCCAoGc291cmNlIqIBChtJbmZlclNjaGVtYUZyb21EYXRhUmVzcG9uc2USJwoGc2NoZW1hGAEgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIYChBkZXRlY3RlZF9jb2x1bW5zGAIgAygJEiwKC3NhbXBsZV9yb3dzGAMgAygLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBISCgp0b3RhbF9yb3dzGAQgASgFIqICCgxEYXRhVGFibGVSb3cSCgoCaWQYASABKAkSEAoIdGFibGVfaWQYAiABKAkSJQoEZGF0YRgDIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSEgoKc29ydF9vcmRlchgEIAEoBRIUCgxpbmdlc3Rpb25faWQYBSABKAkSLwoLaW5nZXN0ZWRfYXQYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCmNyZWF0ZWRfYXQYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhIKCmNyZWF0ZWRfYnkYCSABKAkigQEKGExpc3REYXRhVGFibGVSb3dzUmVxdWVzdBIQCgh0YWJsZV9pZBgBIAEoCRINCgVsaW1pdBgCIAEoBRIOCgZvZmZzZXQYAyABKAUSDgoGc2VhcmNoGAQgASgJEhIKCnNvcnRfZmllbGQYBSABKAkSEAoIc29ydF9kaXIYBiABKAkiVQoZTGlzdERhdGFUYWJsZVJvd3NSZXNwb25zZRIpCgRyb3dzGAEgAygLMhsuYmxvY2tuaW5qYS52MS5EYXRhVGFibGVSb3cSDQoFdG90YWwYAiABKAUiJAoWR2V0RGF0YVRhYmxlUm93UmVxdWVzdBIKCgJpZBgBIAEoCSJDChdHZXREYXRhVGFibGVSb3dSZXNwb25zZRIoCgNyb3cYASABKAsyGy5ibG9ja25pbmphLnYxLkRhdGFUYWJsZVJvdyJ8ChlDcmVhdGVEYXRhVGFibGVSb3dSZXF1ZXN0EhAKCHRhYmxlX2lkGAEgASgJEiUKBGRhdGEYAiABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0EhcKCnNvcnRfb3JkZXIYAyABKAVIAIgBAUINCgtfc29ydF9vcmRlciJGChpDcmVhdGVEYXRhVGFibGVSb3dSZXNwb25zZRIoCgNyb3cYASABKAsyGy5ibG9ja25pbmphLnYxLkRhdGFUYWJsZVJvdyJ2ChlVcGRhdGVEYXRhVGFibGVSb3dSZXF1ZXN0EgoKAmlkGAEgASgJEiUKBGRhdGEYAiABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0EhcKCnNvcnRfb3JkZXIYAyABKAVIAIgBAUINCgtfc29ydF9vcmRlciJGChpVcGRhdGVEYXRhVGFibGVSb3dSZXNwb25zZRIoCgNyb3cYASABKAsyGy5ibG9ja25pbmphLnYxLkRhdGFUYWJsZVJvdyInChlEZWxldGVEYXRhVGFibGVSb3dSZXF1ZXN0EgoKAmlkGAEgASgJIhwKGkRlbGV0ZURhdGFUYWJsZVJvd1Jlc3BvbnNlIi0KHkJ1bGtEZWxldGVEYXRhVGFibGVSb3dzUmVxdWVzdBILCgNpZHMYASADKAkiOAofQnVsa0RlbGV0ZURhdGFUYWJsZVJvd3NSZXNwb25zZRIVCg1kZWxldGVkX2NvdW50GAEgASgFIkAKG1Jlb3JkZXJEYXRhVGFibGVSb3dzUmVxdWVzdBIQCgh0YWJsZV9pZBgBIAEoCRIPCgdyb3dfaWRzGAIgAygJIh4KHFJlb3JkZXJEYXRhVGFibGVSb3dzUmVzcG9uc2UiLQoZQ2xlYXJEYXRhVGFibGVSb3dzUmVxdWVzdBIQCgh0YWJsZV9pZBgBIAEoCSIzChpDbGVhckRhdGFUYWJsZVJvd3NSZXNwb25zZRIVCg1kZWxldGVkX2NvdW50GAEgASgFIu0BChdJbXBvcnRDU1ZQcmV2aWV3UmVxdWVzdBIQCgh0YWJsZV9pZBgBIAEoCRIQCghjc3ZfZGF0YRgCIAEoDBISCgpoYXNfaGVhZGVyGAMgASgIEhEKCWRlbGltaXRlchgEIAEoCRJRCg5jb2x1bW5fbWFwcGluZxgFIAMoCzI5LmJsb2NrbmluamEudjEuSW1wb3J0Q1NWUHJldmlld1JlcXVlc3QuQ29sdW1uTWFwcGluZ0VudHJ5GjQKEkNvbHVtbk1hcHBpbmdFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBItEBChhJbXBvcnRDU1ZQcmV2aWV3UmVzcG9uc2USEgoKdG90YWxfcm93cxgBIAEoBRISCgp2YWxpZF9yb3dzGAIgASgFEhQKDGludmFsaWRfcm93cxgDIAEoBRIqCgZlcnJvcnMYBCADKAsyGi5ibG9ja25pbmphLnYxLkltcG9ydEVycm9yEjEKDHByZXZpZXdfcm93cxgFIAMoCzIbLmJsb2NrbmluamEudjEuRGF0YVRhYmxlUm93EhgKEGRldGVjdGVkX2NvbHVtbnMYBiADKAkiQQoLSW1wb3J0RXJyb3ISEgoKcm93X251bWJlchgBIAEoBRINCgVmaWVsZBgCIAEoCRIPCgdtZXNzYWdlGAMgASgJIogCChBJbXBvcnRDU1ZSZXF1ZXN0EhAKCHRhYmxlX2lkGAEgASgJEhAKCGNzdl9kYXRhGAIgASgMEhIKCmhhc19oZWFkZXIYAyABKAgSEQoJZGVsaW1pdGVyGAQgASgJEkoKDmNvbHVtbl9tYXBwaW5nGAUgAygLMjIuYmxvY2tuaW5qYS52MS5JbXBvcnRDU1ZSZXF1ZXN0LkNvbHVtbk1hcHBpbmdFbnRyeRInCgRtb2RlGAYgASgOMhkuYmxvY2tuaW5qYS52MS5JbXBvcnRNb2RlGjQKEkNvbHVtbk1hcHBpbmdFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIoUBChFJbXBvcnRDU1ZSZXNwb25zZRIWCg5pbXBvcnRlZF9jb3VudBgBIAEoBRIVCg11cGRhdGVkX2NvdW50GAIgASgFEhUKDXNraXBwZWRfY291bnQYAyABKAUSKgoGZXJyb3JzGAQgAygLMhouYmxvY2tuaW5qYS52MS5JbXBvcnRFcnJvciJfChBFeHBvcnRDU1ZSZXF1ZXN0EhAKCHRhYmxlX2lkGAEgASgJEg4KBmZpZWxkcxgCIAMoCRIWCg5pbmNsdWRlX2hlYWRlchgDIAEoCBIRCglkZWxpbWl0ZXIYBCABKAkiSgoRRXhwb3J0Q1NWUmVzcG9uc2USEAoIY3N2X2RhdGEYASABKAwSEAoIZmlsZW5hbWUYAiABKAkSEQoJcm93X2NvdW50GAMgASgFKskCCglGaWVsZFR5cGUSGgoWRklFTERfVFlQRV9VTlNQRUNJRklFRBAAEhUKEUZJRUxEX1RZUEVfU1RSSU5HEAESEwoPRklFTERfVFlQRV9URVhUEAISFQoRRklFTERfVFlQRV9OVU1CRVIQAxIWChJGSUVMRF9UWVBFX0lOVEVHRVIQBBIWChJGSUVMRF9UWVBFX0JPT0xFQU4QBRITCg9GSUVMRF9UWVBFX0RBVEUQBhIXChNGSUVMRF9UWVBFX0RBVEVUSU1FEAcSFAoQRklFTERfVFlQRV9FTUFJTBAIEhIKDkZJRUxEX1RZUEVfVVJMEAkSFQoRRklFTERfVFlQRV9TRUxFQ1QQChIUChBGSUVMRF9UWVBFX01FRElBEAsSEwoPRklFTERfVFlQRV9QQUdFEAwSEwoPRklFTERfVFlQRV9KU09OEA0qcgoKSW1wb3J0TW9kZRIbChdJTVBPUlRfTU9ERV9VTlNQRUNJRklFRBAAEhYKEklNUE9SVF9NT0RFX0FQUEVORBABEhcKE0lNUE9SVF9NT0RFX1JFUExBQ0UQAhIWChJJTVBPUlRfTU9ERV9VUFNFUlQQAzLFDQoRRGF0YVRhYmxlc1NlcnZpY2USXQoOTGlzdERhdGFUYWJsZXMSJC5ibG9ja25pbmphLnYxLkxpc3REYXRhVGFibGVzUmVxdWVzdBolLmJsb2NrbmluamEudjEuTGlzdERhdGFUYWJsZXNSZXNwb25zZRJXCgxHZXREYXRhVGFibGUSIi5ibG9ja25pbmphLnYxLkdldERhdGFUYWJsZVJlcXVlc3QaIy5ibG9ja25pbmphLnYxLkdldERhdGFUYWJsZVJlc3BvbnNlEmAKD0NyZWF0ZURhdGFUYWJsZRIlLmJsb2NrbmluamEudjEuQ3JlYXRlRGF0YVRhYmxlUmVxdWVzdBomLmJsb2NrbmluamEudjEuQ3JlYXRlRGF0YVRhYmxlUmVzcG9uc2USYAoPVXBkYXRlRGF0YVRhYmxlEiUuYmxvY2tuaW5qYS52MS5VcGRhdGVEYXRhVGFibGVSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5VcGRhdGVEYXRhVGFibGVSZXNwb25zZRJgCg9EZWxldGVEYXRhVGFibGUSJS5ibG9ja25pbmphLnYxLkRlbGV0ZURhdGFUYWJsZVJlcXVlc3QaJi5ibG9ja25pbmphLnYxLkRlbGV0ZURhdGFUYWJsZVJlc3BvbnNlEmwKE0luZmVyU2NoZW1hRnJvbURhdGESKS5ibG9ja25pbmphLnYxLkluZmVyU2NoZW1hRnJvbURhdGFSZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5JbmZlclNjaGVtYUZyb21EYXRhUmVzcG9uc2USZgoRTGlzdERhdGFUYWJsZVJvd3MSJy5ibG9ja25pbmphLnYxLkxpc3REYXRhVGFibGVSb3dzUmVxdWVzdBooLmJsb2NrbmluamEudjEuTGlzdERhdGFUYWJsZVJvd3NSZXNwb25zZRJgCg9HZXREYXRhVGFibGVSb3cSJS5ibG9ja25pbmphLnYxLkdldERhdGFUYWJsZVJvd1JlcXVlc3QaJi5ibG9ja25pbmphLnYxLkdldERhdGFUYWJsZVJvd1Jlc3BvbnNlEmkKEkNyZWF0ZURhdGFUYWJsZVJvdxIoLmJsb2NrbmluamEudjEuQ3JlYXRlRGF0YVRhYmxlUm93UmVxdWVzdBopLmJsb2NrbmluamEudjEuQ3JlYXRlRGF0YVRhYmxlUm93UmVzcG9uc2USaQoSVXBkYXRlRGF0YVRhYmxlUm93EiguYmxvY2tuaW5qYS52MS5VcGRhdGVEYXRhVGFibGVSb3dSZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5VcGRhdGVEYXRhVGFibGVSb3dSZXNwb25zZRJpChJEZWxldGVEYXRhVGFibGVSb3cSKC5ibG9ja25pbmphLnYxLkRlbGV0ZURhdGFUYWJsZVJvd1JlcXVlc3QaKS5ibG9ja25pbmphLnYxLkRlbGV0ZURhdGFUYWJsZVJvd1Jlc3BvbnNlEngKF0J1bGtEZWxldGVEYXRhVGFibGVSb3dzEi0uYmxvY2tuaW5qYS52MS5CdWxrRGVsZXRlRGF0YVRhYmxlUm93c1JlcXVlc3QaLi5ibG9ja25pbmphLnYxLkJ1bGtEZWxldGVEYXRhVGFibGVSb3dzUmVzcG9uc2USbwoUUmVvcmRlckRhdGFUYWJsZVJvd3MSKi5ibG9ja25pbmphLnYxLlJlb3JkZXJEYXRhVGFibGVSb3dzUmVxdWVzdBorLmJsb2NrbmluamEudjEuUmVvcmRlckRhdGFUYWJsZVJvd3NSZXNwb25zZRJpChJDbGVhckRhdGFUYWJsZVJvd3MSKC5ibG9ja25pbmphLnYxLkNsZWFyRGF0YVRhYmxlUm93c1JlcXVlc3QaKS5ibG9ja25pbmphLnYxLkNsZWFyRGF0YVRhYmxlUm93c1Jlc3BvbnNlEmMKEEltcG9ydENTVlByZXZpZXcSJi5ibG9ja25pbmphLnYxLkltcG9ydENTVlByZXZpZXdSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5JbXBvcnRDU1ZQcmV2aWV3UmVzcG9uc2USTgoJSW1wb3J0Q1NWEh8uYmxvY2tuaW5qYS52MS5JbXBvcnRDU1ZSZXF1ZXN0GiAuYmxvY2tuaW5qYS52MS5JbXBvcnRDU1ZSZXNwb25zZRJOCglFeHBvcnRDU1YSHy5ibG9ja25pbmphLnYxLkV4cG9ydENTVlJlcXVlc3QaIC5ibG9ja25pbmphLnYxLkV4cG9ydENTVlJlc3BvbnNlQsUBChFjb20uYmxvY2tuaW5qYS52MUIPRGF0YVRhYmxlc1Byb3RvUAFaSmdpdC5kZXYuYWxleGR1bm1vdy5jb20vYmxvY2svbmluamEvaW50ZXJuYWwvYXBpL2Jsb2NrbmluamEvdjE7YmxvY2tuaW5qYXYxogIDQlhYqgINQmxvY2tuaW5qYS5WMcoCDUJsb2NrbmluamFcVjHiAhlCbG9ja25pbmphXFYxXEdQQk1ldGFkYXRh6gIOQmxvY2tuaW5qYTo6VjFiBnByb3RvMw", + [file_google_protobuf_struct, file_google_protobuf_timestamp], +); +/** + * @generated from enum blockninja.v1.FieldType + */ +var FieldType; +(function (FieldType) { + /** + * @generated from enum value: FIELD_TYPE_UNSPECIFIED = 0; + */ + FieldType[(FieldType["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * @generated from enum value: FIELD_TYPE_STRING = 1; + */ + FieldType[(FieldType["STRING"] = 1)] = "STRING"; + /** + * @generated from enum value: FIELD_TYPE_TEXT = 2; + */ + FieldType[(FieldType["TEXT"] = 2)] = "TEXT"; + /** + * @generated from enum value: FIELD_TYPE_NUMBER = 3; + */ + FieldType[(FieldType["NUMBER"] = 3)] = "NUMBER"; + /** + * @generated from enum value: FIELD_TYPE_INTEGER = 4; + */ + FieldType[(FieldType["INTEGER"] = 4)] = "INTEGER"; + /** + * @generated from enum value: FIELD_TYPE_BOOLEAN = 5; + */ + FieldType[(FieldType["BOOLEAN"] = 5)] = "BOOLEAN"; + /** + * @generated from enum value: FIELD_TYPE_DATE = 6; + */ + FieldType[(FieldType["DATE"] = 6)] = "DATE"; + /** + * @generated from enum value: FIELD_TYPE_DATETIME = 7; + */ + FieldType[(FieldType["DATETIME"] = 7)] = "DATETIME"; + /** + * @generated from enum value: FIELD_TYPE_EMAIL = 8; + */ + FieldType[(FieldType["EMAIL"] = 8)] = "EMAIL"; + /** + * @generated from enum value: FIELD_TYPE_URL = 9; + */ + FieldType[(FieldType["URL"] = 9)] = "URL"; + /** + * @generated from enum value: FIELD_TYPE_SELECT = 10; + */ + FieldType[(FieldType["SELECT"] = 10)] = "SELECT"; + /** + * @generated from enum value: FIELD_TYPE_MEDIA = 11; + */ + FieldType[(FieldType["MEDIA"] = 11)] = "MEDIA"; + /** + * @generated from enum value: FIELD_TYPE_PAGE = 12; + */ + FieldType[(FieldType["PAGE"] = 12)] = "PAGE"; + /** + * @generated from enum value: FIELD_TYPE_JSON = 13; + */ + FieldType[(FieldType["JSON"] = 13)] = "JSON"; +})(FieldType || (FieldType = {})); +/** + * @generated from enum blockninja.v1.ImportMode + */ +var ImportMode; +(function (ImportMode) { + /** + * @generated from enum value: IMPORT_MODE_UNSPECIFIED = 0; + */ + ImportMode[(ImportMode["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * Add new rows + * + * @generated from enum value: IMPORT_MODE_APPEND = 1; + */ + ImportMode[(ImportMode["APPEND"] = 1)] = "APPEND"; + /** + * Delete all, then insert + * + * @generated from enum value: IMPORT_MODE_REPLACE = 2; + */ + ImportMode[(ImportMode["REPLACE"] = 2)] = "REPLACE"; + /** + * Update existing, insert new (requires primary_key_field) + * + * @generated from enum value: IMPORT_MODE_UPSERT = 3; + */ + ImportMode[(ImportMode["UPSERT"] = 3)] = "UPSERT"; +})(ImportMode || (ImportMode = {})); +/** + * DataTablesService manages user-defined data tables with JSONB storage. + * Tables have a schema defining fields, and rows store data matching that schema. + * + * @generated from service blockninja.v1.DataTablesService + */ +const DataTablesService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_data_tables, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/data_tables.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Table CRUD + * + * @generated from rpc blockninja.v1.DataTablesService.ListDataTables + */ +DataTablesService.method.listDataTables; +/** + * @generated from rpc blockninja.v1.DataTablesService.GetDataTable + */ +DataTablesService.method.getDataTable; +/** + * @generated from rpc blockninja.v1.DataTablesService.CreateDataTable + */ +DataTablesService.method.createDataTable; +/** + * @generated from rpc blockninja.v1.DataTablesService.UpdateDataTable + */ +DataTablesService.method.updateDataTable; +/** + * @generated from rpc blockninja.v1.DataTablesService.DeleteDataTable + */ +DataTablesService.method.deleteDataTable; +/** + * Schema inference from sample data + * + * @generated from rpc blockninja.v1.DataTablesService.InferSchemaFromData + */ +DataTablesService.method.inferSchemaFromData; +/** + * Row CRUD + * + * @generated from rpc blockninja.v1.DataTablesService.ListDataTableRows + */ +DataTablesService.method.listDataTableRows; +/** + * @generated from rpc blockninja.v1.DataTablesService.GetDataTableRow + */ +DataTablesService.method.getDataTableRow; +/** + * @generated from rpc blockninja.v1.DataTablesService.CreateDataTableRow + */ +DataTablesService.method.createDataTableRow; +/** + * @generated from rpc blockninja.v1.DataTablesService.UpdateDataTableRow + */ +DataTablesService.method.updateDataTableRow; +/** + * @generated from rpc blockninja.v1.DataTablesService.DeleteDataTableRow + */ +DataTablesService.method.deleteDataTableRow; +/** + * @generated from rpc blockninja.v1.DataTablesService.BulkDeleteDataTableRows + */ +DataTablesService.method.bulkDeleteDataTableRows; +/** + * @generated from rpc blockninja.v1.DataTablesService.ReorderDataTableRows + */ +DataTablesService.method.reorderDataTableRows; +/** + * @generated from rpc blockninja.v1.DataTablesService.ClearDataTableRows + */ +DataTablesService.method.clearDataTableRows; +/** + * Import/Export + * + * @generated from rpc blockninja.v1.DataTablesService.ImportCSVPreview + */ +DataTablesService.method.importCSVPreview; +/** + * @generated from rpc blockninja.v1.DataTablesService.ImportCSV + */ +DataTablesService.method.importCSV; +/** + * @generated from rpc blockninja.v1.DataTablesService.ExportCSV + */ +DataTablesService.method.exportCSV; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/embeds.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/embeds.proto. + */ +const file_blockninja_v1_embeds = /*@__PURE__*/ fileDesc( + "ChpibG9ja25pbmphL3YxL2VtYmVkcy5wcm90bxINYmxvY2tuaW5qYS52MSKyAgoNRW1iZWRUZW1wbGF0ZRIKCgJpZBgBIAEoCRILCgNrZXkYAiABKAkSDQoFdGl0bGUYAyABKAkSEwoLZGVzY3JpcHRpb24YBCABKAkSDAoEaWNvbhgFIAEoCRIYChBkYXRhX3NvdXJjZV90eXBlGAYgASgJEhEKCXRhYmxlX2tleRgHIAEoCRIQCgh0YWJsZV9pZBgIIAEoCRITCgtsYWJlbF9maWVsZBgJIAEoCRIVCgh0ZW1wbGF0ZRgKIAEoCUgAiAEBEi4KCmNyZWF0ZWRfYXQYCyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYDCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgsKCV90ZW1wbGF0ZSIbChlMaXN0RW1iZWRUZW1wbGF0ZXNSZXF1ZXN0Ik0KGkxpc3RFbWJlZFRlbXBsYXRlc1Jlc3BvbnNlEi8KCXRlbXBsYXRlcxgBIAMoCzIcLmJsb2NrbmluamEudjEuRW1iZWRUZW1wbGF0ZSIlChdHZXRFbWJlZFRlbXBsYXRlUmVxdWVzdBIKCgJpZBgBIAEoCSJKChhHZXRFbWJlZFRlbXBsYXRlUmVzcG9uc2USLgoIdGVtcGxhdGUYASABKAsyHC5ibG9ja25pbmphLnYxLkVtYmVkVGVtcGxhdGUirwEKGkNyZWF0ZUVtYmVkVGVtcGxhdGVSZXF1ZXN0Eg0KBXRpdGxlGAEgASgJEgsKA2tleRgCIAEoCRIMCgRpY29uGAMgASgJEhMKC2Rlc2NyaXB0aW9uGAQgASgJEhgKEGRhdGFfc291cmNlX3R5cGUYBSABKAkSEQoJdGFibGVfa2V5GAYgASgJEhMKC2xhYmVsX2ZpZWxkGAcgASgJEhAKCHRlbXBsYXRlGAggASgJIk0KG0NyZWF0ZUVtYmVkVGVtcGxhdGVSZXNwb25zZRIuCgh0ZW1wbGF0ZRgBIAEoCzIcLmJsb2NrbmluamEudjEuRW1iZWRUZW1wbGF0ZSKuAQoaVXBkYXRlRW1iZWRUZW1wbGF0ZVJlcXVlc3QSCgoCaWQYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEaWNvbhgDIAEoCRITCgtkZXNjcmlwdGlvbhgEIAEoCRIYChBkYXRhX3NvdXJjZV90eXBlGAUgASgJEhEKCXRhYmxlX2tleRgGIAEoCRITCgtsYWJlbF9maWVsZBgHIAEoCRIQCgh0ZW1wbGF0ZRgIIAEoCSJNChtVcGRhdGVFbWJlZFRlbXBsYXRlUmVzcG9uc2USLgoIdGVtcGxhdGUYASABKAsyHC5ibG9ja25pbmphLnYxLkVtYmVkVGVtcGxhdGUiKAoaRGVsZXRlRW1iZWRUZW1wbGF0ZVJlcXVlc3QSCgoCaWQYASABKAkiHQobRGVsZXRlRW1iZWRUZW1wbGF0ZVJlc3BvbnNlIkwKGlNlYXJjaERhdGFUYWJsZVJvd3NSZXF1ZXN0EhAKCHRhYmxlX2lkGAEgASgJEg0KBXF1ZXJ5GAIgASgJEg0KBWxpbWl0GAMgASgFIkcKEkRhdGFUYWJsZVJvd1Jlc3VsdBIKCgJpZBgBIAEoCRIlCgRkYXRhGAIgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdCJOChtTZWFyY2hEYXRhVGFibGVSb3dzUmVzcG9uc2USLwoEcm93cxgBIAMoCzIhLmJsb2NrbmluamEudjEuRGF0YVRhYmxlUm93UmVzdWx0MpcFCg1FbWJlZHNTZXJ2aWNlEmkKEkxpc3RFbWJlZFRlbXBsYXRlcxIoLmJsb2NrbmluamEudjEuTGlzdEVtYmVkVGVtcGxhdGVzUmVxdWVzdBopLmJsb2NrbmluamEudjEuTGlzdEVtYmVkVGVtcGxhdGVzUmVzcG9uc2USYwoQR2V0RW1iZWRUZW1wbGF0ZRImLmJsb2NrbmluamEudjEuR2V0RW1iZWRUZW1wbGF0ZVJlcXVlc3QaJy5ibG9ja25pbmphLnYxLkdldEVtYmVkVGVtcGxhdGVSZXNwb25zZRJsChNDcmVhdGVFbWJlZFRlbXBsYXRlEikuYmxvY2tuaW5qYS52MS5DcmVhdGVFbWJlZFRlbXBsYXRlUmVxdWVzdBoqLmJsb2NrbmluamEudjEuQ3JlYXRlRW1iZWRUZW1wbGF0ZVJlc3BvbnNlEmwKE1VwZGF0ZUVtYmVkVGVtcGxhdGUSKS5ibG9ja25pbmphLnYxLlVwZGF0ZUVtYmVkVGVtcGxhdGVSZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5VcGRhdGVFbWJlZFRlbXBsYXRlUmVzcG9uc2USbAoTRGVsZXRlRW1iZWRUZW1wbGF0ZRIpLmJsb2NrbmluamEudjEuRGVsZXRlRW1iZWRUZW1wbGF0ZVJlcXVlc3QaKi5ibG9ja25pbmphLnYxLkRlbGV0ZUVtYmVkVGVtcGxhdGVSZXNwb25zZRJsChNTZWFyY2hEYXRhVGFibGVSb3dzEikuYmxvY2tuaW5qYS52MS5TZWFyY2hEYXRhVGFibGVSb3dzUmVxdWVzdBoqLmJsb2NrbmluamEudjEuU2VhcmNoRGF0YVRhYmxlUm93c1Jlc3BvbnNlQsEBChFjb20uYmxvY2tuaW5qYS52MUILRW1iZWRzUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_google_protobuf_struct, file_google_protobuf_timestamp], +); +/** + * @generated from service blockninja.v1.EmbedsService + */ +const EmbedsService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_embeds, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/embeds.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * @generated from rpc blockninja.v1.EmbedsService.ListEmbedTemplates + */ +EmbedsService.method.listEmbedTemplates; +/** + * @generated from rpc blockninja.v1.EmbedsService.GetEmbedTemplate + */ +EmbedsService.method.getEmbedTemplate; +/** + * @generated from rpc blockninja.v1.EmbedsService.CreateEmbedTemplate + */ +EmbedsService.method.createEmbedTemplate; +/** + * @generated from rpc blockninja.v1.EmbedsService.UpdateEmbedTemplate + */ +EmbedsService.method.updateEmbedTemplate; +/** + * @generated from rpc blockninja.v1.EmbedsService.DeleteEmbedTemplate + */ +EmbedsService.method.deleteEmbedTemplate; +/** + * @generated from rpc blockninja.v1.EmbedsService.SearchDataTableRows + */ +EmbedsService.method.searchDataTableRows; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/engagement.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/engagement.proto. + */ +const file_blockninja_v1_engagement = /*@__PURE__*/ fileDesc( + "Ch5ibG9ja25pbmphL3YxL2VuZ2FnZW1lbnQucHJvdG8SDWJsb2NrbmluamEudjEivwEKGUVuZ2FnZW1lbnRUaW1lUmFuZ2VGaWx0ZXISMgoGcHJlc2V0GAEgASgOMiIuYmxvY2tuaW5qYS52MS5FbmdhZ2VtZW50VGltZVJhbmdlEi4KBXN0YXJ0GAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgAiAEBEiwKA2VuZBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIAYgBAUIICgZfc3RhcnRCBgoEX2VuZCKFAQoYR2V0UG9zdEVuZ2FnZW1lbnRSZXF1ZXN0Eg8KB3BhZ2VfaWQYASABKAkSPAoKdGltZV9yYW5nZRgCIAEoCzIoLmJsb2NrbmluamEudjEuRW5nYWdlbWVudFRpbWVSYW5nZUZpbHRlchIaChJpbmNsdWRlX2NvbXBhcmlzb24YAyABKAgixgMKGUdldFBvc3RFbmdhZ2VtZW50UmVzcG9uc2USFQoNdG90YWxfcmVhZGVycxgBIAEoAxITCgtjb21wbGV0aW9ucxgCIAEoAxIYChBhdmdfc2Nyb2xsX2RlcHRoGAMgASgBEhkKEXJlYWRfdGhyb3VnaF9yYXRlGAQgASgBEhgKEGF2Z190aW1lX3NlY29uZHMYBSABKAESHwoXYXZnX2FjdGl2ZV90aW1lX3NlY29uZHMYBiABKAESIAoYYXZnX3JlYWRpbmdfdmVsb2NpdHlfd3BtGAcgASgBEhgKEGVuZ2FnZW1lbnRfc2NvcmUYCCABKAESGQoRYXZnX3F1YWxpdHlfc2NvcmUYCSABKAESJQoYcmVhZF90aHJvdWdoX3JhdGVfY2hhbmdlGAogASgBSACIAQESHAoPYXZnX3RpbWVfY2hhbmdlGAsgASgBSAGIAQESJAoXZW5nYWdlbWVudF9zY29yZV9jaGFuZ2UYDCABKAFIAogBAUIbChlfcmVhZF90aHJvdWdoX3JhdGVfY2hhbmdlQhIKEF9hdmdfdGltZV9jaGFuZ2VCGgoYX2VuZ2FnZW1lbnRfc2NvcmVfY2hhbmdlImcKFkdldFNjcm9sbEZ1bm5lbFJlcXVlc3QSDwoHcGFnZV9pZBgBIAEoCRI8Cgp0aW1lX3JhbmdlGAIgASgLMiguYmxvY2tuaW5qYS52MS5FbmdhZ2VtZW50VGltZVJhbmdlRmlsdGVyImcKD1Njcm9sbE1pbGVzdG9uZRIPCgdwZXJjZW50GAEgASgFEhAKCHZpc2l0b3JzGAIgASgDEhsKE3BlcmNlbnRhZ2Vfb2ZfdG90YWwYAyABKAESFAoMZHJvcG9mZl9yYXRlGAQgASgBImUKF0dldFNjcm9sbEZ1bm5lbFJlc3BvbnNlEjIKCm1pbGVzdG9uZXMYASADKAsyHi5ibG9ja25pbmphLnYxLlNjcm9sbE1pbGVzdG9uZRIWCg50b3RhbF92aXNpdG9ycxgCIAEoAyJsChtHZXRTZWN0aW9uRW5nYWdlbWVudFJlcXVlc3QSDwoHcGFnZV9pZBgBIAEoCRI8Cgp0aW1lX3JhbmdlGAIgASgLMiguYmxvY2tuaW5qYS52MS5FbmdhZ2VtZW50VGltZVJhbmdlRmlsdGVyIpEBCg5TZWN0aW9uTWV0cmljcxIUCgxoZWFkaW5nX3RleHQYASABKAkSEgoKaGVhZGluZ19pZBgCIAEoCRIVCg1oZWFkaW5nX2xldmVsGAMgASgFEg0KBXZpZXdzGAQgASgDEhkKEWF2Z19kd2VsbF9zZWNvbmRzGAUgASgBEhQKDHJlcmVhZF9jb3VudBgGIAEoAyJPChxHZXRTZWN0aW9uRW5nYWdlbWVudFJlc3BvbnNlEi8KCHNlY3Rpb25zGAEgAygLMh0uYmxvY2tuaW5qYS52MS5TZWN0aW9uTWV0cmljcyJnChZHZXRCb3VuY2VQb2ludHNSZXF1ZXN0Eg8KB3BhZ2VfaWQYASABKAkSPAoKdGltZV9yYW5nZRgCIAEoCzIoLmJsb2NrbmluamEudjEuRW5nYWdlbWVudFRpbWVSYW5nZUZpbHRlciJICgtCb3VuY2VQb2ludBIWCg5wZXJjZW50X2J1Y2tldBgBIAEoBRINCgVjb3VudBgCIAEoAxISCgpwZXJjZW50YWdlGAMgASgBIn4KF0dldEJvdW5jZVBvaW50c1Jlc3BvbnNlEioKBnBvaW50cxgBIAMoCzIaLmJsb2NrbmluamEudjEuQm91bmNlUG9pbnQSIAoYbW9zdF9jb21tb25fZXhpdF9wZXJjZW50GAIgASgFEhUKDXRvdGFsX2JvdW5jZXMYAyABKAMibwoeR2V0SW50ZXJhY3Rpb25CcmVha2Rvd25SZXF1ZXN0Eg8KB3BhZ2VfaWQYASABKAkSPAoKdGltZV9yYW5nZRgCIAEoCzIoLmJsb2NrbmluamEudjEuRW5nYWdlbWVudFRpbWVSYW5nZUZpbHRlciLIAQofR2V0SW50ZXJhY3Rpb25CcmVha2Rvd25SZXNwb25zZRIXCg90ZXh0X3NlbGVjdGlvbnMYASABKAMSEwoLY29weV9ldmVudHMYAiABKAMSHAoUaW50ZXJuYWxfbGlua19jbGlja3MYAyABKAMSHAoUZXh0ZXJuYWxfbGlua19jbGlja3MYBCABKAMSGgoSaW1hZ2VfaW50ZXJhY3Rpb25zGAUgASgDEh8KF2NvZGVfYmxvY2tfaW50ZXJhY3Rpb25zGAYgASgDIm0KHEdldEZydXN0cmF0aW9uU2lnbmFsc1JlcXVlc3QSDwoHcGFnZV9pZBgBIAEoCRI8Cgp0aW1lX3JhbmdlGAIgASgLMiguYmxvY2tuaW5qYS52MS5FbmdhZ2VtZW50VGltZVJhbmdlRmlsdGVyIpUBCh1HZXRGcnVzdHJhdGlvblNpZ25hbHNSZXNwb25zZRITCgtyYWdlX2NsaWNrcxgBIAEoAxITCgtkZWFkX2NsaWNrcxgCIAEoAxIXCg9lcnJhdGljX3Njcm9sbHMYAyABKAMSFwoPc2VhcmNoX2F0dGVtcHRzGAQgASgDEhgKEGZydXN0cmF0aW9uX3JhdGUYBSABKAEibQocR2V0TmF2aWdhdGlvblBhdHRlcm5zUmVxdWVzdBIPCgdwYWdlX2lkGAEgASgJEjwKCnRpbWVfcmFuZ2UYAiABKAsyKC5ibG9ja25pbmphLnYxLkVuZ2FnZW1lbnRUaW1lUmFuZ2VGaWx0ZXIijwEKHUdldE5hdmlnYXRpb25QYXR0ZXJuc1Jlc3BvbnNlEhIKCnRvY19jbGlja3MYASABKAMSFAoMYW5jaG9yX2p1bXBzGAIgASgDEhYKDnByaW50X2F0dGVtcHRzGAMgASgDEhQKDHpvb21fY2hhbmdlcxgEIAEoAxIWCg50b2NfdXNhZ2VfcmF0ZRgFIAEoASJBCh9HZXRUb3BIaWdobGlnaHRlZFBocmFzZXNSZXF1ZXN0Eg8KB3BhZ2VfaWQYASABKAkSDQoFbGltaXQYAiABKAUiVQoRSGlnaGxpZ2h0ZWRQaHJhc2USDgoGcGhyYXNlGAEgASgJEhcKD3NlY3Rpb25faGVhZGluZxgCIAEoCRIXCg9oaWdobGlnaHRfY291bnQYAyABKAMiVQogR2V0VG9wSGlnaGxpZ2h0ZWRQaHJhc2VzUmVzcG9uc2USMQoHcGhyYXNlcxgBIAMoCzIgLmJsb2NrbmluamEudjEuSGlnaGxpZ2h0ZWRQaHJhc2UiLwocR2V0UmV0dXJuVmlzaXRvclN0YXRzUmVxdWVzdBIPCgdwYWdlX2lkGAEgASgJIq0BCh1HZXRSZXR1cm5WaXNpdG9yU3RhdHNSZXNwb25zZRIdChV0b3RhbF9yZXR1cm5fdmlzaXRvcnMYASABKAMSGwoTdG90YWxfcmV0dXJuX3Zpc2l0cxgCIAEoAxIfChdhdmdfdmlzaXRzX3Blcl9yZXR1cm5lchgDIAEoARITCgtyZXR1cm5fcmF0ZRgEIAEoARIaChJpbXByb3ZlZF9vbl9yZXR1cm4YBSABKAMiegoaR2V0VG9wRW5nYWdpbmdQb3N0c1JlcXVlc3QSPAoKdGltZV9yYW5nZRgBIAEoCzIoLmJsb2NrbmluamEudjEuRW5nYWdlbWVudFRpbWVSYW5nZUZpbHRlchINCgVsaW1pdBgCIAEoBRIPCgdzb3J0X2J5GAMgASgJIr4BCg9Ub3BFbmdhZ2luZ1Bvc3QSDwoHcGFnZV9pZBgBIAEoCRIRCglwYWdlX3BhdGgYAiABKAkSDQoFdGl0bGUYAyABKAkSDwoHcmVhZGVycxgEIAEoAxIYChBhdmdfc2Nyb2xsX2RlcHRoGAUgASgBEhkKEXJlYWRfdGhyb3VnaF9yYXRlGAYgASgBEhgKEGF2Z190aW1lX3NlY29uZHMYByABKAESGAoQZW5nYWdlbWVudF9zY29yZRgIIAEoASJMChtHZXRUb3BFbmdhZ2luZ1Bvc3RzUmVzcG9uc2USLQoFcG9zdHMYASADKAsyHi5ibG9ja25pbmphLnYxLlRvcEVuZ2FnaW5nUG9zdCKUAQoeR2V0RW5nYWdlbWVudFRpbWVzZXJpZXNSZXF1ZXN0Eg8KB3BhZ2VfaWQYASABKAkSPAoKdGltZV9yYW5nZRgCIAEoCzIoLmJsb2NrbmluamEudjEuRW5nYWdlbWVudFRpbWVSYW5nZUZpbHRlchIOCgZtZXRyaWMYAyABKAkSEwoLZ3JhbnVsYXJpdHkYBCABKAkiVAoZRW5nYWdlbWVudFRpbWVzZXJpZXNQb2ludBIoCgR0aW1lGAEgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBINCgV2YWx1ZRgCIAEoASJrCh9HZXRFbmdhZ2VtZW50VGltZXNlcmllc1Jlc3BvbnNlEjgKBnBvaW50cxgBIAMoCzIoLmJsb2NrbmluamEudjEuRW5nYWdlbWVudFRpbWVzZXJpZXNQb2ludBIOCgZtZXRyaWMYAiABKAkiaQoYR2V0UXVhbGl0eVNpZ25hbHNSZXF1ZXN0Eg8KB3BhZ2VfaWQYASABKAkSPAoKdGltZV9yYW5nZRgCIAEoCzIoLmJsb2NrbmluamEudjEuRW5nYWdlbWVudFRpbWVSYW5nZUZpbHRlciK9AQoZR2V0UXVhbGl0eVNpZ25hbHNSZXNwb25zZRIZChFhdmdfcXVhbGl0eV9zY29yZRgBIAEoARIbChNoaWdoX3F1YWxpdHlfdmlzaXRzGAIgASgDEh0KFW1lZGl1bV9xdWFsaXR5X3Zpc2l0cxgDIAEoAxIaChJsb3dfcXVhbGl0eV92aXNpdHMYBCABKAMSGQoRc3VzcGljaW91c192aXNpdHMYBSABKAMSEgoKaHVtYW5fcmF0ZRgGIAEoASJpChhHZXRSZWFkZXJTZWdtZW50c1JlcXVlc3QSDwoHcGFnZV9pZBgBIAEoCRI8Cgp0aW1lX3JhbmdlGAIgASgLMiguYmxvY2tuaW5qYS52MS5FbmdhZ2VtZW50VGltZVJhbmdlRmlsdGVyIlUKDVJlYWRlclNlZ21lbnQSDAoEbmFtZRgBIAEoCRITCgtkZXNjcmlwdGlvbhgCIAEoCRINCgVjb3VudBgDIAEoAxISCgpwZXJjZW50YWdlGAQgASgBImIKGUdldFJlYWRlclNlZ21lbnRzUmVzcG9uc2USLgoIc2VnbWVudHMYASADKAsyHC5ibG9ja25pbmphLnYxLlJlYWRlclNlZ21lbnQSFQoNdG90YWxfcmVhZGVycxgCIAEoAyqMAgoTRW5nYWdlbWVudFRpbWVSYW5nZRIlCiFFTkdBR0VNRU5UX1RJTUVfUkFOR0VfVU5TUEVDSUZJRUQQABIfChtFTkdBR0VNRU5UX1RJTUVfUkFOR0VfVE9EQVkQARIjCh9FTkdBR0VNRU5UX1RJTUVfUkFOR0VfWUVTVEVSREFZEAISIAocRU5HQUdFTUVOVF9USU1FX1JBTkdFXzdfREFZUxADEiEKHUVOR0FHRU1FTlRfVElNRV9SQU5HRV8zMF9EQVlTEAQSIQodRU5HQUdFTUVOVF9USU1FX1JBTkdFXzkwX0RBWVMQBRIgChxFTkdBR0VNRU5UX1RJTUVfUkFOR0VfQ1VTVE9NEAYyuwsKEUVuZ2FnZW1lbnRTZXJ2aWNlEmYKEUdldFBvc3RFbmdhZ2VtZW50EicuYmxvY2tuaW5qYS52MS5HZXRQb3N0RW5nYWdlbWVudFJlcXVlc3QaKC5ibG9ja25pbmphLnYxLkdldFBvc3RFbmdhZ2VtZW50UmVzcG9uc2USYAoPR2V0U2Nyb2xsRnVubmVsEiUuYmxvY2tuaW5qYS52MS5HZXRTY3JvbGxGdW5uZWxSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5HZXRTY3JvbGxGdW5uZWxSZXNwb25zZRJvChRHZXRTZWN0aW9uRW5nYWdlbWVudBIqLmJsb2NrbmluamEudjEuR2V0U2VjdGlvbkVuZ2FnZW1lbnRSZXF1ZXN0GisuYmxvY2tuaW5qYS52MS5HZXRTZWN0aW9uRW5nYWdlbWVudFJlc3BvbnNlEmAKD0dldEJvdW5jZVBvaW50cxIlLmJsb2NrbmluamEudjEuR2V0Qm91bmNlUG9pbnRzUmVxdWVzdBomLmJsb2NrbmluamEudjEuR2V0Qm91bmNlUG9pbnRzUmVzcG9uc2USeAoXR2V0SW50ZXJhY3Rpb25CcmVha2Rvd24SLS5ibG9ja25pbmphLnYxLkdldEludGVyYWN0aW9uQnJlYWtkb3duUmVxdWVzdBouLmJsb2NrbmluamEudjEuR2V0SW50ZXJhY3Rpb25CcmVha2Rvd25SZXNwb25zZRJyChVHZXRGcnVzdHJhdGlvblNpZ25hbHMSKy5ibG9ja25pbmphLnYxLkdldEZydXN0cmF0aW9uU2lnbmFsc1JlcXVlc3QaLC5ibG9ja25pbmphLnYxLkdldEZydXN0cmF0aW9uU2lnbmFsc1Jlc3BvbnNlEnIKFUdldE5hdmlnYXRpb25QYXR0ZXJucxIrLmJsb2NrbmluamEudjEuR2V0TmF2aWdhdGlvblBhdHRlcm5zUmVxdWVzdBosLmJsb2NrbmluamEudjEuR2V0TmF2aWdhdGlvblBhdHRlcm5zUmVzcG9uc2USewoYR2V0VG9wSGlnaGxpZ2h0ZWRQaHJhc2VzEi4uYmxvY2tuaW5qYS52MS5HZXRUb3BIaWdobGlnaHRlZFBocmFzZXNSZXF1ZXN0Gi8uYmxvY2tuaW5qYS52MS5HZXRUb3BIaWdobGlnaHRlZFBocmFzZXNSZXNwb25zZRJyChVHZXRSZXR1cm5WaXNpdG9yU3RhdHMSKy5ibG9ja25pbmphLnYxLkdldFJldHVyblZpc2l0b3JTdGF0c1JlcXVlc3QaLC5ibG9ja25pbmphLnYxLkdldFJldHVyblZpc2l0b3JTdGF0c1Jlc3BvbnNlEmwKE0dldFRvcEVuZ2FnaW5nUG9zdHMSKS5ibG9ja25pbmphLnYxLkdldFRvcEVuZ2FnaW5nUG9zdHNSZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5HZXRUb3BFbmdhZ2luZ1Bvc3RzUmVzcG9uc2USeAoXR2V0RW5nYWdlbWVudFRpbWVzZXJpZXMSLS5ibG9ja25pbmphLnYxLkdldEVuZ2FnZW1lbnRUaW1lc2VyaWVzUmVxdWVzdBouLmJsb2NrbmluamEudjEuR2V0RW5nYWdlbWVudFRpbWVzZXJpZXNSZXNwb25zZRJmChFHZXRRdWFsaXR5U2lnbmFscxInLmJsb2NrbmluamEudjEuR2V0UXVhbGl0eVNpZ25hbHNSZXF1ZXN0GiguYmxvY2tuaW5qYS52MS5HZXRRdWFsaXR5U2lnbmFsc1Jlc3BvbnNlEmYKEUdldFJlYWRlclNlZ21lbnRzEicuYmxvY2tuaW5qYS52MS5HZXRSZWFkZXJTZWdtZW50c1JlcXVlc3QaKC5ibG9ja25pbmphLnYxLkdldFJlYWRlclNlZ21lbnRzUmVzcG9uc2VCxQEKEWNvbS5ibG9ja25pbmphLnYxQg9FbmdhZ2VtZW50UHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_google_protobuf_timestamp], +); +/** + * @generated from enum blockninja.v1.EngagementTimeRange + */ +var EngagementTimeRange; +(function (EngagementTimeRange) { + /** + * @generated from enum value: ENGAGEMENT_TIME_RANGE_UNSPECIFIED = 0; + */ + EngagementTimeRange[(EngagementTimeRange["ENGAGEMENT_TIME_RANGE_UNSPECIFIED"] = 0)] = "ENGAGEMENT_TIME_RANGE_UNSPECIFIED"; + /** + * @generated from enum value: ENGAGEMENT_TIME_RANGE_TODAY = 1; + */ + EngagementTimeRange[(EngagementTimeRange["ENGAGEMENT_TIME_RANGE_TODAY"] = 1)] = "ENGAGEMENT_TIME_RANGE_TODAY"; + /** + * @generated from enum value: ENGAGEMENT_TIME_RANGE_YESTERDAY = 2; + */ + EngagementTimeRange[(EngagementTimeRange["ENGAGEMENT_TIME_RANGE_YESTERDAY"] = 2)] = "ENGAGEMENT_TIME_RANGE_YESTERDAY"; + /** + * @generated from enum value: ENGAGEMENT_TIME_RANGE_7_DAYS = 3; + */ + EngagementTimeRange[(EngagementTimeRange["ENGAGEMENT_TIME_RANGE_7_DAYS"] = 3)] = "ENGAGEMENT_TIME_RANGE_7_DAYS"; + /** + * @generated from enum value: ENGAGEMENT_TIME_RANGE_30_DAYS = 4; + */ + EngagementTimeRange[(EngagementTimeRange["ENGAGEMENT_TIME_RANGE_30_DAYS"] = 4)] = "ENGAGEMENT_TIME_RANGE_30_DAYS"; + /** + * @generated from enum value: ENGAGEMENT_TIME_RANGE_90_DAYS = 5; + */ + EngagementTimeRange[(EngagementTimeRange["ENGAGEMENT_TIME_RANGE_90_DAYS"] = 5)] = "ENGAGEMENT_TIME_RANGE_90_DAYS"; + /** + * @generated from enum value: ENGAGEMENT_TIME_RANGE_CUSTOM = 6; + */ + EngagementTimeRange[(EngagementTimeRange["ENGAGEMENT_TIME_RANGE_CUSTOM"] = 6)] = "ENGAGEMENT_TIME_RANGE_CUSTOM"; +})(EngagementTimeRange || (EngagementTimeRange = {})); +/** + * EngagementService provides blog post reading engagement analytics + * + * @generated from service blockninja.v1.EngagementService + */ +const EngagementService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_engagement, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/engagement.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Get engagement overview for a specific post + * + * @generated from rpc blockninja.v1.EngagementService.GetPostEngagement + */ +EngagementService.method.getPostEngagement; +/** + * Get scroll funnel breakdown (25%, 50%, 75%, 100%) + * + * @generated from rpc blockninja.v1.EngagementService.GetScrollFunnel + */ +EngagementService.method.getScrollFunnel; +/** + * Get section-level engagement (per heading dwell times) + * + * @generated from rpc blockninja.v1.EngagementService.GetSectionEngagement + */ +EngagementService.method.getSectionEngagement; +/** + * Get bounce point analysis (where readers exit) + * + * @generated from rpc blockninja.v1.EngagementService.GetBouncePoints + */ +EngagementService.method.getBouncePoints; +/** + * Get interaction breakdown (clicks, selections, copies) + * + * @generated from rpc blockninja.v1.EngagementService.GetInteractionBreakdown + */ +EngagementService.method.getInteractionBreakdown; +/** + * Get frustration signals (rage clicks, dead clicks, erratic scrolling) + * + * @generated from rpc blockninja.v1.EngagementService.GetFrustrationSignals + */ +EngagementService.method.getFrustrationSignals; +/** + * Get navigation patterns (TOC usage, anchor jumps, print attempts) + * + * @generated from rpc blockninja.v1.EngagementService.GetNavigationPatterns + */ +EngagementService.method.getNavigationPatterns; +/** + * Get top highlighted phrases (popular text selections) + * + * @generated from rpc blockninja.v1.EngagementService.GetTopHighlightedPhrases + */ +EngagementService.method.getTopHighlightedPhrases; +/** + * Get return visitor statistics (multi-session tracking) + * + * @generated from rpc blockninja.v1.EngagementService.GetReturnVisitorStats + */ +EngagementService.method.getReturnVisitorStats; +/** + * Get top engaging posts ranked by engagement score + * + * @generated from rpc blockninja.v1.EngagementService.GetTopEngagingPosts + */ +EngagementService.method.getTopEngagingPosts; +/** + * Get engagement timeseries data + * + * @generated from rpc blockninja.v1.EngagementService.GetEngagementTimeseries + */ +EngagementService.method.getEngagementTimeseries; +/** + * Get quality signals breakdown (bot detection, quality scores) + * + * @generated from rpc blockninja.v1.EngagementService.GetQualitySignals + */ +EngagementService.method.getQualitySignals; +/** + * Get reader segments (scanners, readers, deep readers) + * + * @generated from rpc blockninja.v1.EngagementService.GetReaderSegments + */ +EngagementService.method.getReaderSegments; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/icons.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/icons.proto. + */ +const file_blockninja_v1_icons = /*@__PURE__*/ fileDesc( + "ChlibG9ja25pbmphL3YxL2ljb25zLnByb3RvEg1ibG9ja25pbmphLnYxItEBCghJY29uUGFjaxIKCgJpZBgBIAEoCRIMCgRzbHVnGAIgASgJEgwKBG5hbWUYAyABKAkSEwoLZGVzY3JpcHRpb24YBCABKAkSEwoLc291cmNlX3R5cGUYBSABKAkSDwoHdmVyc2lvbhgGIAEoCRISCgppY29uX2NvdW50GAcgASgFEhIKCmlzX2VuYWJsZWQYCCABKAgSEgoKc3ByaXRlX3VybBgJIAEoCRISCgpjcmVhdGVkX2F0GAogASgJEhIKCnVwZGF0ZWRfYXQYCyABKAkifgoESWNvbhIRCglwYWNrX3NsdWcYASABKAkSDAoEbmFtZRgCIAEoCRINCgV0aXRsZRgDIAEoCRIQCghjYXRlZ29yeRgEIAEoCRIMCgR0YWdzGAUgAygJEhEKCWhleF9jb2xvchgGIAEoCRITCgtwcmV2aWV3X3N2ZxgHIAEoCSKlAQoNQXZhaWxhYmxlUGFjaxIMCgRzbHVnGAEgASgJEgwKBG5hbWUYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSFgoObGF0ZXN0X3ZlcnNpb24YBCABKAkSEgoKaWNvbl9jb3VudBgFIAEoBRIQCghob21lcGFnZRgGIAEoCRIPCgdsaWNlbnNlGAcgASgJEhQKDGlzX2luc3RhbGxlZBgIIAEoCCIsChRMaXN0SWNvblBhY2tzUmVxdWVzdBIUCgxlbmFibGVkX29ubHkYASABKAgiPwoVTGlzdEljb25QYWNrc1Jlc3BvbnNlEiYKBXBhY2tzGAEgAygLMhcuYmxvY2tuaW5qYS52MS5JY29uUGFjayIiChJHZXRJY29uUGFja1JlcXVlc3QSDAoEc2x1ZxgBIAEoCSJgChNHZXRJY29uUGFja1Jlc3BvbnNlEiUKBHBhY2sYASABKAsyFy5ibG9ja25pbmphLnYxLkljb25QYWNrEiIKBWljb25zGAIgAygLMhMuYmxvY2tuaW5qYS52MS5JY29uIiUKFUVuYWJsZUljb25QYWNrUmVxdWVzdBIMCgRzbHVnGAEgASgJIj8KFkVuYWJsZUljb25QYWNrUmVzcG9uc2USJQoEcGFjaxgBIAEoCzIXLmJsb2NrbmluamEudjEuSWNvblBhY2siJgoWRGlzYWJsZUljb25QYWNrUmVxdWVzdBIMCgRzbHVnGAEgASgJIkAKF0Rpc2FibGVJY29uUGFja1Jlc3BvbnNlEiUKBHBhY2sYASABKAsyFy5ibG9ja25pbmphLnYxLkljb25QYWNrIhsKGUxpc3RBdmFpbGFibGVQYWNrc1JlcXVlc3QiSQoaTGlzdEF2YWlsYWJsZVBhY2tzUmVzcG9uc2USKwoFcGFja3MYASADKAsyHC5ibG9ja25pbmphLnYxLkF2YWlsYWJsZVBhY2siMwoSSW5zdGFsbFBhY2tSZXF1ZXN0EgwKBHNsdWcYASABKAkSDwoHdmVyc2lvbhgCIAEoCSI8ChNJbnN0YWxsUGFja1Jlc3BvbnNlEiUKBHBhY2sYASABKAsyFy5ibG9ja25pbmphLnYxLkljb25QYWNrIiQKFFVuaW5zdGFsbFBhY2tSZXF1ZXN0EgwKBHNsdWcYASABKAkiFwoVVW5pbnN0YWxsUGFja1Jlc3BvbnNlIkUKFVVwbG9hZEljb25QYWNrUmVxdWVzdBIMCgRuYW1lGAEgASgJEgwKBHNsdWcYAiABKAkSEAoIemlwX2RhdGEYAyABKAwiPwoWVXBsb2FkSWNvblBhY2tSZXNwb25zZRIlCgRwYWNrGAEgASgLMhcuYmxvY2tuaW5qYS52MS5JY29uUGFjayJnChJTZWFyY2hJY29uc1JlcXVlc3QSDQoFcXVlcnkYASABKAkSEQoJcGFja19zbHVnGAIgASgJEhAKCGNhdGVnb3J5GAMgASgJEg0KBWxpbWl0GAQgASgFEg4KBm9mZnNldBgFIAEoBSJIChNTZWFyY2hJY29uc1Jlc3BvbnNlEiIKBWljb25zGAEgAygLMhMuYmxvY2tuaW5qYS52MS5JY29uEg0KBXRvdGFsGAIgASgFIjYKDkdldEljb25SZXF1ZXN0EhEKCXBhY2tfc2x1ZxgBIAEoCRIRCglpY29uX25hbWUYAiABKAkiQQoPR2V0SWNvblJlc3BvbnNlEiEKBGljb24YASABKAsyEy5ibG9ja25pbmphLnYxLkljb24SCwoDc3ZnGAIgASgJIi4KGUxpc3RJY29uQ2F0ZWdvcmllc1JlcXVlc3QSEQoJcGFja19zbHVnGAEgASgJImIKGkxpc3RJY29uQ2F0ZWdvcmllc1Jlc3BvbnNlEi8KCmNhdGVnb3JpZXMYASADKAsyGy5ibG9ja25pbmphLnYxLkljb25DYXRlZ29yeRITCgt0b3RhbF9pY29ucxgCIAEoBSIrCgxJY29uQ2F0ZWdvcnkSDAoEbmFtZRgBIAEoCRINCgVjb3VudBgCIAEoBTKICAoMSWNvbnNTZXJ2aWNlEloKDUxpc3RJY29uUGFja3MSIy5ibG9ja25pbmphLnYxLkxpc3RJY29uUGFja3NSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5MaXN0SWNvblBhY2tzUmVzcG9uc2USVAoLR2V0SWNvblBhY2sSIS5ibG9ja25pbmphLnYxLkdldEljb25QYWNrUmVxdWVzdBoiLmJsb2NrbmluamEudjEuR2V0SWNvblBhY2tSZXNwb25zZRJdCg5FbmFibGVJY29uUGFjaxIkLmJsb2NrbmluamEudjEuRW5hYmxlSWNvblBhY2tSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5FbmFibGVJY29uUGFja1Jlc3BvbnNlEmAKD0Rpc2FibGVJY29uUGFjaxIlLmJsb2NrbmluamEudjEuRGlzYWJsZUljb25QYWNrUmVxdWVzdBomLmJsb2NrbmluamEudjEuRGlzYWJsZUljb25QYWNrUmVzcG9uc2USaQoSTGlzdEF2YWlsYWJsZVBhY2tzEiguYmxvY2tuaW5qYS52MS5MaXN0QXZhaWxhYmxlUGFja3NSZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5MaXN0QXZhaWxhYmxlUGFja3NSZXNwb25zZRJUCgtJbnN0YWxsUGFjaxIhLmJsb2NrbmluamEudjEuSW5zdGFsbFBhY2tSZXF1ZXN0GiIuYmxvY2tuaW5qYS52MS5JbnN0YWxsUGFja1Jlc3BvbnNlEloKDVVuaW5zdGFsbFBhY2sSIy5ibG9ja25pbmphLnYxLlVuaW5zdGFsbFBhY2tSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5Vbmluc3RhbGxQYWNrUmVzcG9uc2USXQoOVXBsb2FkSWNvblBhY2sSJC5ibG9ja25pbmphLnYxLlVwbG9hZEljb25QYWNrUmVxdWVzdBolLmJsb2NrbmluamEudjEuVXBsb2FkSWNvblBhY2tSZXNwb25zZRJUCgtTZWFyY2hJY29ucxIhLmJsb2NrbmluamEudjEuU2VhcmNoSWNvbnNSZXF1ZXN0GiIuYmxvY2tuaW5qYS52MS5TZWFyY2hJY29uc1Jlc3BvbnNlEkgKB0dldEljb24SHS5ibG9ja25pbmphLnYxLkdldEljb25SZXF1ZXN0Gh4uYmxvY2tuaW5qYS52MS5HZXRJY29uUmVzcG9uc2USaQoSTGlzdEljb25DYXRlZ29yaWVzEiguYmxvY2tuaW5qYS52MS5MaXN0SWNvbkNhdGVnb3JpZXNSZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5MaXN0SWNvbkNhdGVnb3JpZXNSZXNwb25zZULAAQoRY29tLmJsb2NrbmluamEudjFCCkljb25zUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", +); +/** + * IconsService manages icon packs and provides icon search functionality. + * + * @generated from service blockninja.v1.IconsService + */ +const IconsService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_icons, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/icons.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Pack management + * + * @generated from rpc blockninja.v1.IconsService.ListIconPacks + */ +IconsService.method.listIconPacks; +/** + * @generated from rpc blockninja.v1.IconsService.GetIconPack + */ +IconsService.method.getIconPack; +/** + * @generated from rpc blockninja.v1.IconsService.EnableIconPack + */ +IconsService.method.enableIconPack; +/** + * @generated from rpc blockninja.v1.IconsService.DisableIconPack + */ +IconsService.method.disableIconPack; +/** + * Registry (download on demand) + * + * @generated from rpc blockninja.v1.IconsService.ListAvailablePacks + */ +IconsService.method.listAvailablePacks; +/** + * @generated from rpc blockninja.v1.IconsService.InstallPack + */ +IconsService.method.installPack; +/** + * @generated from rpc blockninja.v1.IconsService.UninstallPack + */ +IconsService.method.uninstallPack; +/** + * Custom upload + * + * @generated from rpc blockninja.v1.IconsService.UploadIconPack + */ +IconsService.method.uploadIconPack; +/** + * Icon search (for icon picker) + * + * @generated from rpc blockninja.v1.IconsService.SearchIcons + */ +IconsService.method.searchIcons; +/** + * Get single icon SVG (for preview) + * + * @generated from rpc blockninja.v1.IconsService.GetIcon + */ +IconsService.method.getIcon; +/** + * List categories for a pack + * + * @generated from rpc blockninja.v1.IconsService.ListIconCategories + */ +IconsService.method.listIconCategories; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/image_analysis.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/image_analysis.proto. + */ +const file_blockninja_v1_image_analysis = /*@__PURE__*/ fileDesc( + "CiJibG9ja25pbmphL3YxL2ltYWdlX2FuYWx5c2lzLnByb3RvEg1ibG9ja25pbmphLnYxIlkKE0FuYWx5emVJbWFnZVJlcXVlc3QSEAoIbWVkaWFfaWQYASABKAkSGQoRZ2VuZXJhdGVfYWx0X3RleHQYAiABKAgSFQoNZ2VuZXJhdGVfdGFncxgDIAEoCCI2ChRBbmFseXplSW1hZ2VSZXNwb25zZRIQCghhbHRfdGV4dBgBIAEoCRIMCgR0YWdzGAIgAygJMm8KFEltYWdlQW5hbHlzaXNTZXJ2aWNlElcKDEFuYWx5emVJbWFnZRIiLmJsb2NrbmluamEudjEuQW5hbHl6ZUltYWdlUmVxdWVzdBojLmJsb2NrbmluamEudjEuQW5hbHl6ZUltYWdlUmVzcG9uc2VCyAEKEWNvbS5ibG9ja25pbmphLnYxQhJJbWFnZUFuYWx5c2lzUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", +); +/** + * ImageAnalysisService provides AI-powered image analysis capabilities + * + * @generated from service blockninja.v1.ImageAnalysisService + */ +const ImageAnalysisService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_image_analysis, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/image_analysis.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Analyze an image to generate alt text and/or tags + * + * @generated from rpc blockninja.v1.ImageAnalysisService.AnalyzeImage + */ +ImageAnalysisService.method.analyzeImage; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/import.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/import.proto. + */ +const file_blockninja_v1_import = /*@__PURE__*/ fileDesc( + "ChpibG9ja25pbmphL3YxL2ltcG9ydC5wcm90bxINYmxvY2tuaW5qYS52MSIxCh1QcmV2aWV3V29yZFByZXNzSW1wb3J0UmVxdWVzdBIQCgh3eHJfZGF0YRgBIAEoDCKrAgoeUHJldmlld1dvcmRQcmVzc0ltcG9ydFJlc3BvbnNlEhIKCnNpdGVfdGl0bGUYASABKAkSEAoIc2l0ZV91cmwYAiABKAkSFQoNYXV0aG9yc19jb3VudBgDIAEoBRIYChBjYXRlZ29yaWVzX2NvdW50GAQgASgFEhIKCnRhZ3NfY291bnQYBSABKAUSEwoLdG90YWxfaXRlbXMYBiABKAUSVQoNaXRlbXNfYnlfdHlwZRgHIAMoCzI+LmJsb2NrbmluamEudjEuUHJldmlld1dvcmRQcmVzc0ltcG9ydFJlc3BvbnNlLkl0ZW1zQnlUeXBlRW50cnkaMgoQSXRlbXNCeVR5cGVFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAU6AjgBImIKFkltcG9ydFdvcmRQcmVzc1JlcXVlc3QSEAoId3hyX2RhdGEYASABKAwSNgoHb3B0aW9ucxgCIAEoCzIlLmJsb2NrbmluamEudjEuSW1wb3J0V29yZFByZXNzT3B0aW9ucyKcAQoWSW1wb3J0V29yZFByZXNzT3B0aW9ucxIXCg9pbXBvcnRfYXNfZHJhZnQYASABKAgSFwoPZG93bmxvYWRfaW1hZ2VzGAIgASgIEhUKDXNraXBfZXhpc3RpbmcYAyABKAgSHgoWY2FsY3VsYXRlX3JlYWRpbmdfdGltZRgEIAEoCBIZChFkZWZhdWx0X2F1dGhvcl9pZBgFIAEoCSKeAQoXSW1wb3J0V29yZFByZXNzUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCBIWCg5wb3N0c19pbXBvcnRlZBgCIAEoBRIVCg1wb3N0c19za2lwcGVkGAMgASgFEhcKD2ltYWdlc19pbXBvcnRlZBgEIAEoBRIaChJjYXRlZ29yaWVzX2NyZWF0ZWQYBSABKAUSDgoGZXJyb3JzGAYgAygJMugBCg1JbXBvcnRTZXJ2aWNlEnUKFlByZXZpZXdXb3JkUHJlc3NJbXBvcnQSLC5ibG9ja25pbmphLnYxLlByZXZpZXdXb3JkUHJlc3NJbXBvcnRSZXF1ZXN0Gi0uYmxvY2tuaW5qYS52MS5QcmV2aWV3V29yZFByZXNzSW1wb3J0UmVzcG9uc2USYAoPSW1wb3J0V29yZFByZXNzEiUuYmxvY2tuaW5qYS52MS5JbXBvcnRXb3JkUHJlc3NSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5JbXBvcnRXb3JkUHJlc3NSZXNwb25zZULBAQoRY29tLmJsb2NrbmluamEudjFCC0ltcG9ydFByb3RvUAFaSmdpdC5kZXYuYWxleGR1bm1vdy5jb20vYmxvY2svbmluamEvaW50ZXJuYWwvYXBpL2Jsb2NrbmluamEvdjE7YmxvY2tuaW5qYXYxogIDQlhYqgINQmxvY2tuaW5qYS5WMcoCDUJsb2NrbmluamFcVjHiAhlCbG9ja25pbmphXFYxXEdQQk1ldGFkYXRh6gIOQmxvY2tuaW5qYTo6VjFiBnByb3RvMw", +); +/** + * ImportService handles importing content from external sources + * + * @generated from service blockninja.v1.ImportService + */ +const ImportService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_import, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/import.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Preview what a WordPress WXR export contains before importing + * + * @generated from rpc blockninja.v1.ImportService.PreviewWordPressImport + */ +ImportService.method.previewWordPressImport; +/** + * Import content from WordPress WXR export + * + * @generated from rpc blockninja.v1.ImportService.ImportWordPress + */ +ImportService.method.importWordPress; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/ingestion.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/ingestion.proto. + */ +const file_blockninja_v1_ingestion = /*@__PURE__*/ fileDesc( + "Ch1ibG9ja25pbmphL3YxL2luZ2VzdGlvbi5wcm90bxINYmxvY2tuaW5qYS52MSK7CAoRSW5nZXN0aW9uUGlwZWxpbmUSCgoCaWQYASABKAkSFAoMcGlwZWxpbmVfa2V5GAIgASgJEgwKBG5hbWUYAyABKAkSEwoLZGVzY3JpcHRpb24YBCABKAkSFwoPdGFyZ2V0X3RhYmxlX2lkGAUgASgJEhkKEXRhcmdldF90YWJsZV9uYW1lGAYgASgJEjQKDmluZ2VzdGlvbl90eXBlGAcgASgOMhwuYmxvY2tuaW5qYS52MS5Jbmdlc3Rpb25UeXBlEg8KB2VuYWJsZWQYCCABKAgSMgoIc2NoZWR1bGUYCSABKA4yIC5ibG9ja25pbmphLnYxLkluZ2VzdGlvblNjaGVkdWxlEi8KC2xhc3RfcnVuX2F0GAogASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIXCg9sYXN0X3J1bl9zdGF0dXMYCyABKAkSFgoObGFzdF9ydW5fZXJyb3IYDCABKAkSFQoNbGFzdF9ydW5fcm93cxgNIAEoBRIvCgtuZXh0X3J1bl9hdBgOIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASMwoSdHJhbnNmb3JtX3BpcGVsaW5lGA8gASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIWCg51cHNlcnRfZW5hYmxlZBgQIAEoCBIYChB1cHNlcnRfa2V5X2ZpZWxkGBEgASgJEjYKDndlYmhvb2tfY29uZmlnGBIgASgLMhwuYmxvY2tuaW5qYS52MS5XZWJob29rQ29uZmlnSAASLgoKYXBpX2NvbmZpZxgTIAEoCzIYLmJsb2NrbmluamEudjEuQVBJQ29uZmlnSAASOwoRZGF0YV9tb2RlbF9jb25maWcYFCABKAsyHi5ibG9ja25pbmphLnYxLkRhdGFNb2RlbENvbmZpZ0gAEi4KCmNyZWF0ZWRfYXQYFSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYFiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhIKCmNyZWF0ZWRfYnkYFyABKAkSOQoYZHJhZnRfdHJhbnNmb3JtX3BpcGVsaW5lGBggASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIjChtwdWJsaXNoZWRfdHJhbnNmb3JtX3ZlcnNpb24YGSABKAUSNAoQZHJhZnRfdXBkYXRlZF9hdBgaIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFAoMaXNfYWN0aXZhdGVkGBsgASgIEh4KFmJhdGNoX2R1cGxpY2F0ZV9wb2xpY3kYHCABKAkSGAoQY2FwdHVyZV9vdmVyZmxvdxgdIAEoCBIYChBwZW5kaW5nX2ZhaWx1cmVzGB4gASgFQggKBmNvbmZpZyKXAQoNV2ViaG9va0NvbmZpZxITCgt3ZWJob29rX2tleRgBIAEoCRIZChFyZXF1aXJlX3NpZ25hdHVyZRgCIAEoCBITCgthbGxvd2VkX2lwcxgDIAMoCRISCgphY2NlcHRfY3N2GAQgASgIEhUKDWNzdl9kZWxpbWl0ZXIYBSABKAkSFgoOY3N2X2hhc19oZWFkZXIYBiABKAgiqgQKCUFQSUNvbmZpZxIQCghiYXNlX3VybBgBIAEoCRIVCg1lbmRwb2ludF9wYXRoGAIgASgJEhMKC2h0dHBfbWV0aG9kGAMgASgJEi0KCWF1dGhfdHlwZRgEIAEoDjIaLmJsb2NrbmluamEudjEuQVBJQXV0aFR5cGUSGAoQYXV0aF9oZWFkZXJfbmFtZRgFIAEoCRIYChBhdXRoX3F1ZXJ5X3BhcmFtGAYgASgJEjYKB2hlYWRlcnMYByADKAsyJS5ibG9ja25pbmphLnYxLkFQSUNvbmZpZy5IZWFkZXJzRW50cnkSPwoMcXVlcnlfcGFyYW1zGAggAygLMikuYmxvY2tuaW5qYS52MS5BUElDb25maWcuUXVlcnlQYXJhbXNFbnRyeRIUCgxyZXF1ZXN0X2JvZHkYCSABKAkSEQoJZGF0YV9wYXRoGAogASgJEhUKDWhhc19tb3JlX3BhdGgYCyABKAkSEwoLY3Vyc29yX3BhdGgYDCABKAkSGAoQY3Vyc29yX3N0YXRlX2tleRgNIAEoCRIVCg1hdXRvX3BhZ2luYXRlGA4gASgIEhkKEW1heF9wYWdlc19wZXJfcnVuGA8gASgFGi4KDEhlYWRlcnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBGjIKEFF1ZXJ5UGFyYW1zRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJzCg9EYXRhTW9kZWxDb25maWcSLwoHc291cmNlcxgBIAMoCzIeLmJsb2NrbmluamEudjEuRGF0YU1vZGVsU291cmNlEi8KDm91dHB1dF9tYXBwaW5nGAIgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdCJfCg9EYXRhTW9kZWxTb3VyY2USFQoNZGF0YXNvdXJjZV9pZBgBIAEoCRINCgVhbGlhcxgCIAEoCRImCgVxdWVyeRgDIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3Qi6QIKDEluZ2VzdGlvblJ1bhIKCgJpZBgBIAEoCRITCgtwaXBlbGluZV9pZBgCIAEoCRIuCgpzdGFydGVkX2F0GAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIwCgxjb21wbGV0ZWRfYXQYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg4KBnN0YXR1cxgFIAEoCRIVCg1lcnJvcl9tZXNzYWdlGAYgASgJEhQKDHJvd3NfZmV0Y2hlZBgHIAEoBRIVCg1yb3dzX2luc2VydGVkGAggASgFEhQKDHJvd3NfdXBkYXRlZBgJIAEoBRIUCgxyb3dzX3NraXBwZWQYCiABKAUSEwoLcm93c19mYWlsZWQYDiABKAUSEQoJc291cmNlX2lwGAsgASgJEhQKDHBheWxvYWRfc2l6ZRgMIAEoBRIYChByZXNwb25zZV90aW1lX21zGA0gASgFItUBCg5Jbmdlc3Rpb25TdGF0cxISCgp0b3RhbF9ydW5zGAEgASgFEhcKD3N1Y2Nlc3NmdWxfcnVucxgCIAEoBRITCgtmYWlsZWRfcnVucxgDIAEoBRIXCg9hdmdfZHVyYXRpb25fbXMYBCABKAESGwoTdG90YWxfcm93c19pbnNlcnRlZBgFIAEoAxIaChJ0b3RhbF9yb3dzX3VwZGF0ZWQYBiABKAMSLwoLbGFzdF9ydW5fYXQYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIo0BCh1MaXN0SW5nZXN0aW9uUGlwZWxpbmVzUmVxdWVzdBIXCg90YXJnZXRfdGFibGVfaWQYASABKAkSNAoOaW5nZXN0aW9uX3R5cGUYAiABKA4yHC5ibG9ja25pbmphLnYxLkluZ2VzdGlvblR5cGUSDQoFbGltaXQYAyABKAUSDgoGb2Zmc2V0GAQgASgFImQKHkxpc3RJbmdlc3Rpb25QaXBlbGluZXNSZXNwb25zZRIzCglwaXBlbGluZXMYASADKAsyIC5ibG9ja25pbmphLnYxLkluZ2VzdGlvblBpcGVsaW5lEg0KBXRvdGFsGAIgASgFIikKG0dldEluZ2VzdGlvblBpcGVsaW5lUmVxdWVzdBIKCgJpZBgBIAEoCSJSChxHZXRJbmdlc3Rpb25QaXBlbGluZVJlc3BvbnNlEjIKCHBpcGVsaW5lGAEgASgLMiAuYmxvY2tuaW5qYS52MS5Jbmdlc3Rpb25QaXBlbGluZSKPBAoeQ3JlYXRlSW5nZXN0aW9uUGlwZWxpbmVSZXF1ZXN0EhQKDHBpcGVsaW5lX2tleRgBIAEoCRIMCgRuYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEhcKD3RhcmdldF90YWJsZV9pZBgEIAEoCRI0Cg5pbmdlc3Rpb25fdHlwZRgFIAEoDjIcLmJsb2NrbmluamEudjEuSW5nZXN0aW9uVHlwZRIPCgdlbmFibGVkGAYgASgIEjIKCHNjaGVkdWxlGAcgASgOMiAuYmxvY2tuaW5qYS52MS5Jbmdlc3Rpb25TY2hlZHVsZRIzChJ0cmFuc2Zvcm1fcGlwZWxpbmUYCCABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0EhYKDnVwc2VydF9lbmFibGVkGAkgASgIEhgKEHVwc2VydF9rZXlfZmllbGQYCiABKAkSPAoOd2ViaG9va19jb25maWcYCyABKAsyIi5ibG9ja25pbmphLnYxLkNyZWF0ZVdlYmhvb2tDb25maWdIABI0CgphcGlfY29uZmlnGAwgASgLMh4uYmxvY2tuaW5qYS52MS5DcmVhdGVBUElDb25maWdIABI7ChFkYXRhX21vZGVsX2NvbmZpZxgNIAEoCzIeLmJsb2NrbmluamEudjEuRGF0YU1vZGVsQ29uZmlnSABCCAoGY29uZmlnIp0BChNDcmVhdGVXZWJob29rQ29uZmlnEhMKC3dlYmhvb2tfa2V5GAEgASgJEhkKEXJlcXVpcmVfc2lnbmF0dXJlGAIgASgIEhMKC2FsbG93ZWRfaXBzGAMgAygJEhIKCmFjY2VwdF9jc3YYBCABKAgSFQoNY3N2X2RlbGltaXRlchgFIAEoCRIWCg5jc3ZfaGFzX2hlYWRlchgGIAEoCCLRBAoPQ3JlYXRlQVBJQ29uZmlnEhAKCGJhc2VfdXJsGAEgASgJEhUKDWVuZHBvaW50X3BhdGgYAiABKAkSEwoLaHR0cF9tZXRob2QYAyABKAkSLQoJYXV0aF90eXBlGAQgASgOMhouYmxvY2tuaW5qYS52MS5BUElBdXRoVHlwZRIYChBhdXRoX2hlYWRlcl9uYW1lGAUgASgJEhgKEGF1dGhfcXVlcnlfcGFyYW0YBiABKAkSEwoLYXV0aF9zZWNyZXQYByABKAkSPAoHaGVhZGVycxgIIAMoCzIrLmJsb2NrbmluamEudjEuQ3JlYXRlQVBJQ29uZmlnLkhlYWRlcnNFbnRyeRJFCgxxdWVyeV9wYXJhbXMYCSADKAsyLy5ibG9ja25pbmphLnYxLkNyZWF0ZUFQSUNvbmZpZy5RdWVyeVBhcmFtc0VudHJ5EhQKDHJlcXVlc3RfYm9keRgKIAEoCRIRCglkYXRhX3BhdGgYCyABKAkSFQoNaGFzX21vcmVfcGF0aBgMIAEoCRITCgtjdXJzb3JfcGF0aBgNIAEoCRIYChBjdXJzb3Jfc3RhdGVfa2V5GA4gASgJEhUKDWF1dG9fcGFnaW5hdGUYDyABKAgSGQoRbWF4X3BhZ2VzX3Blcl9ydW4YECABKAUaLgoMSGVhZGVyc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEaMgoQUXVlcnlQYXJhbXNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIm0KH0NyZWF0ZUluZ2VzdGlvblBpcGVsaW5lUmVzcG9uc2USMgoIcGlwZWxpbmUYASABKAsyIC5ibG9ja25pbmphLnYxLkluZ2VzdGlvblBpcGVsaW5lEhYKDndlYmhvb2tfc2VjcmV0GAIgASgJIq4ECh5VcGRhdGVJbmdlc3Rpb25QaXBlbGluZVJlcXVlc3QSCgoCaWQYASABKAkSEQoEbmFtZRgCIAEoCUgBiAEBEhgKC2Rlc2NyaXB0aW9uGAMgASgJSAKIAQESFAoHZW5hYmxlZBgEIAEoCEgDiAEBEjcKCHNjaGVkdWxlGAUgASgOMiAuYmxvY2tuaW5qYS52MS5Jbmdlc3Rpb25TY2hlZHVsZUgEiAEBEjMKEnRyYW5zZm9ybV9waXBlbGluZRgGIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSGwoOdXBzZXJ0X2VuYWJsZWQYByABKAhIBYgBARIdChB1cHNlcnRfa2V5X2ZpZWxkGAggASgJSAaIAQESPAoOd2ViaG9va19jb25maWcYCSABKAsyIi5ibG9ja25pbmphLnYxLlVwZGF0ZVdlYmhvb2tDb25maWdIABI0CgphcGlfY29uZmlnGAogASgLMh4uYmxvY2tuaW5qYS52MS5VcGRhdGVBUElDb25maWdIABI7ChFkYXRhX21vZGVsX2NvbmZpZxgLIAEoCzIeLmJsb2NrbmluamEudjEuRGF0YU1vZGVsQ29uZmlnSABCCAoGY29uZmlnQgcKBV9uYW1lQg4KDF9kZXNjcmlwdGlvbkIKCghfZW5hYmxlZEILCglfc2NoZWR1bGVCEQoPX3Vwc2VydF9lbmFibGVkQhMKEV91cHNlcnRfa2V5X2ZpZWxkIuYBChNVcGRhdGVXZWJob29rQ29uZmlnEh4KEXJlcXVpcmVfc2lnbmF0dXJlGAEgASgISACIAQESEwoLYWxsb3dlZF9pcHMYAiADKAkSFwoKYWNjZXB0X2NzdhgDIAEoCEgBiAEBEhoKDWNzdl9kZWxpbWl0ZXIYBCABKAlIAogBARIbCg5jc3ZfaGFzX2hlYWRlchgFIAEoCEgDiAEBQhQKEl9yZXF1aXJlX3NpZ25hdHVyZUINCgtfYWNjZXB0X2NzdkIQCg5fY3N2X2RlbGltaXRlckIRCg9fY3N2X2hhc19oZWFkZXIijAcKD1VwZGF0ZUFQSUNvbmZpZxIVCghiYXNlX3VybBgBIAEoCUgAiAEBEhoKDWVuZHBvaW50X3BhdGgYAiABKAlIAYgBARIYCgtodHRwX21ldGhvZBgDIAEoCUgCiAEBEjIKCWF1dGhfdHlwZRgEIAEoDjIaLmJsb2NrbmluamEudjEuQVBJQXV0aFR5cGVIA4gBARIdChBhdXRoX2hlYWRlcl9uYW1lGAUgASgJSASIAQESHQoQYXV0aF9xdWVyeV9wYXJhbRgGIAEoCUgFiAEBEhgKC2F1dGhfc2VjcmV0GAcgASgJSAaIAQESPAoHaGVhZGVycxgIIAMoCzIrLmJsb2NrbmluamEudjEuVXBkYXRlQVBJQ29uZmlnLkhlYWRlcnNFbnRyeRJFCgxxdWVyeV9wYXJhbXMYCSADKAsyLy5ibG9ja25pbmphLnYxLlVwZGF0ZUFQSUNvbmZpZy5RdWVyeVBhcmFtc0VudHJ5EhkKDHJlcXVlc3RfYm9keRgKIAEoCUgHiAEBEhYKCWRhdGFfcGF0aBgLIAEoCUgIiAEBEhoKDWhhc19tb3JlX3BhdGgYDCABKAlICYgBARIYCgtjdXJzb3JfcGF0aBgNIAEoCUgKiAEBEh0KEGN1cnNvcl9zdGF0ZV9rZXkYDiABKAlIC4gBARIaCg1hdXRvX3BhZ2luYXRlGA8gASgISAyIAQESHgoRbWF4X3BhZ2VzX3Blcl9ydW4YECABKAVIDYgBARouCgxIZWFkZXJzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ARoyChBRdWVyeVBhcmFtc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAFCCwoJX2Jhc2VfdXJsQhAKDl9lbmRwb2ludF9wYXRoQg4KDF9odHRwX21ldGhvZEIMCgpfYXV0aF90eXBlQhMKEV9hdXRoX2hlYWRlcl9uYW1lQhMKEV9hdXRoX3F1ZXJ5X3BhcmFtQg4KDF9hdXRoX3NlY3JldEIPCg1fcmVxdWVzdF9ib2R5QgwKCl9kYXRhX3BhdGhCEAoOX2hhc19tb3JlX3BhdGhCDgoMX2N1cnNvcl9wYXRoQhMKEV9jdXJzb3Jfc3RhdGVfa2V5QhAKDl9hdXRvX3BhZ2luYXRlQhQKEl9tYXhfcGFnZXNfcGVyX3J1biJVCh9VcGRhdGVJbmdlc3Rpb25QaXBlbGluZVJlc3BvbnNlEjIKCHBpcGVsaW5lGAEgASgLMiAuYmxvY2tuaW5qYS52MS5Jbmdlc3Rpb25QaXBlbGluZSIsCh5EZWxldGVJbmdlc3Rpb25QaXBlbGluZVJlcXVlc3QSCgoCaWQYASABKAkiIQofRGVsZXRlSW5nZXN0aW9uUGlwZWxpbmVSZXNwb25zZSItChZSdW5Jbmdlc3Rpb25Ob3dSZXF1ZXN0EhMKC3BpcGVsaW5lX2lkGAEgASgJIkMKF1J1bkluZ2VzdGlvbk5vd1Jlc3BvbnNlEigKA3J1bhgBIAEoCzIbLmJsb2NrbmluamEudjEuSW5nZXN0aW9uUnVuIj0KF1ByZXZpZXdJbmdlc3Rpb25SZXF1ZXN0EhMKC3BpcGVsaW5lX2lkGAEgASgJEg0KBWxpbWl0GAIgASgFImEKGFByZXZpZXdJbmdlc3Rpb25SZXNwb25zZRImCgVpdGVtcxgBIAMoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSDQoFdG90YWwYAiABKAUSDgoGZXJyb3JzGAMgAygJIk4KGExpc3RJbmdlc3Rpb25SdW5zUmVxdWVzdBITCgtwaXBlbGluZV9pZBgBIAEoCRINCgVsaW1pdBgCIAEoBRIOCgZvZmZzZXQYAyABKAUiVQoZTGlzdEluZ2VzdGlvblJ1bnNSZXNwb25zZRIpCgRydW5zGAEgAygLMhsuYmxvY2tuaW5qYS52MS5Jbmdlc3Rpb25SdW4SDQoFdG90YWwYAiABKAUiJAoWR2V0SW5nZXN0aW9uUnVuUmVxdWVzdBIKCgJpZBgBIAEoCSJDChdHZXRJbmdlc3Rpb25SdW5SZXNwb25zZRIoCgNydW4YASABKAsyGy5ibG9ja25pbmphLnYxLkluZ2VzdGlvblJ1biIvChhHZXRJbmdlc3Rpb25TdGF0c1JlcXVlc3QSEwoLcGlwZWxpbmVfaWQYASABKAkiSQoZR2V0SW5nZXN0aW9uU3RhdHNSZXNwb25zZRIsCgVzdGF0cxgBIAEoCzIdLmJsb2NrbmluamEudjEuSW5nZXN0aW9uU3RhdHMiNQoeUmVnZW5lcmF0ZVdlYmhvb2tTZWNyZXRSZXF1ZXN0EhMKC3BpcGVsaW5lX2lkGAEgASgJIjEKH1JlZ2VuZXJhdGVXZWJob29rU2VjcmV0UmVzcG9uc2USDgoGc2VjcmV0GAEgASgJIjAKGUdldFdlYmhvb2tFbmRwb2ludFJlcXVlc3QSEwoLcGlwZWxpbmVfaWQYASABKAkiRwoaR2V0V2ViaG9va0VuZHBvaW50UmVzcG9uc2USFAoMZW5kcG9pbnRfdXJsGAEgASgJEhMKC3dlYmhvb2tfa2V5GAIgASgJIi8KGFRlc3RBUElDb25uZWN0aW9uUmVxdWVzdBITCgtwaXBlbGluZV9pZBgBIAEoCSKDAQoZVGVzdEFQSUNvbm5lY3Rpb25SZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEg0KBWVycm9yGAIgASgJEhcKD3Jlc3BvbnNlX3N0YXR1cxgDIAEoBRIYChByZXNwb25zZV90aW1lX21zGAQgASgFEhMKC2l0ZW1zX2ZvdW5kGAUgASgFIjAKGVJlc2V0UGlwZWxpbmVTdGF0ZVJlcXVlc3QSEwoLcGlwZWxpbmVfaWQYASABKAkiHAoaUmVzZXRQaXBlbGluZVN0YXRlUmVzcG9uc2UiaAoWVGVzdFRyYW5zbGF0aW9uUmVxdWVzdBITCgtwaXBlbGluZV9pZBgBIAEoCRIWCg5zYW1wbGVfcGF5bG9hZBgCIAEoDBIOCgZpc19jc3YYAyABKAgSEQoJdXNlX2RyYWZ0GAQgASgIInkKF1Rlc3RUcmFuc2xhdGlvblJlc3BvbnNlEi8KBHJvd3MYASADKAsyIS5ibG9ja25pbmphLnYxLlRyYW5zbGF0aW9uVGVzdFJvdxINCgV0b3RhbBgCIAEoBRIOCgZwYXNzZWQYAyABKAUSDgoGZmFpbGVkGAQgASgFIt0BChJUcmFuc2xhdGlvblRlc3RSb3cSEQoJcm93X2luZGV4GAEgASgFEiYKBWlucHV0GAIgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBInCgZvdXRwdXQYAyABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0EhMKC3Jlc29sdmVkX2lkGAQgASgJEg4KBnBhc3NlZBgFIAEoCBIpCgZlcnJvcnMYBiADKAsyGS5ibG9ja25pbmphLnYxLkZpZWxkRXJyb3ISEwoLZmFpbGVkX3N0ZXAYByABKAkiSAoKRmllbGRFcnJvchIMCgRzdGVwGAEgASgJEg0KBWZpZWxkGAIgASgJEg8KB21lc3NhZ2UYAyABKAkSDAoEY29kZRgEIAEoCSJaChtTYXZlRHJhZnRUcmFuc2xhdGlvblJlcXVlc3QSEwoLcGlwZWxpbmVfaWQYASABKAkSJgoFc3RlcHMYAiABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0Ik4KHFNhdmVEcmFmdFRyYW5zbGF0aW9uUmVzcG9uc2USLgoKdXBkYXRlZF9hdBgBIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiMAoZUHVibGlzaFRyYW5zbGF0aW9uUmVxdWVzdBITCgtwaXBlbGluZV9pZBgBIAEoCSJfChpQdWJsaXNoVHJhbnNsYXRpb25SZXNwb25zZRIPCgd2ZXJzaW9uGAEgASgFEjAKDHB1Ymxpc2hlZF9hdBgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiKgoTRGlzY2FyZERyYWZ0UmVxdWVzdBITCgtwaXBlbGluZV9pZBgBIAEoCSIWChREaXNjYXJkRHJhZnRSZXNwb25zZSIuChdBY3RpdmF0ZVBpcGVsaW5lUmVxdWVzdBITCgtwaXBlbGluZV9pZBgBIAEoCSI+ChhBY3RpdmF0ZVBpcGVsaW5lUmVzcG9uc2USEQoJYWN0aXZhdGVkGAEgASgIEg8KB3dhcm5pbmcYAiABKAkiMAoZRGVhY3RpdmF0ZVBpcGVsaW5lUmVxdWVzdBITCgtwaXBlbGluZV9pZBgBIAEoCSIxChpEZWFjdGl2YXRlUGlwZWxpbmVSZXNwb25zZRITCgtkZWFjdGl2YXRlZBgBIAEoCCI4CiFHZXRQaXBlbGluZUFjdGl2YXRpb25TdGF0ZVJlcXVlc3QSEwoLcGlwZWxpbmVfaWQYASABKAkigwEKIkdldFBpcGVsaW5lQWN0aXZhdGlvblN0YXRlUmVzcG9uc2USFAoMaXNfYWN0aXZhdGVkGAEgASgIEg8KB2VuYWJsZWQYAiABKAgSIwobcHVibGlzaGVkX3RyYW5zZm9ybV92ZXJzaW9uGAMgASgFEhEKCWhhc19kcmFmdBgEIAEoCCL+AgoQSW5nZXN0aW9uRmFpbHVyZRIKCgJpZBgBIAEoCRIOCgZydW5faWQYAiABKAkSEwoLcGlwZWxpbmVfaWQYAyABKAkSEQoJcm93X2luZGV4GAQgASgFEikKCHJhd19kYXRhGAUgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIxChB0cmFuc2Zvcm1lZF9kYXRhGAYgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIpCgZlcnJvcnMYByADKAsyGS5ibG9ja25pbmphLnYxLkZpZWxkRXJyb3ISEwoLc3RlcF9mYWlsZWQYCCABKAkSDgoGc3RhdHVzGAkgASgJEi4KCmNyZWF0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi8KC3JlcGxheWVkX2F0GAsgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIXCg9yZXBsYXllZF9ydW5faWQYDCABKAkicgocTGlzdEluZ2VzdGlvbkZhaWx1cmVzUmVxdWVzdBITCgtwaXBlbGluZV9pZBgBIAEoCRIOCgZzdGF0dXMYAiABKAkSDgoGcnVuX2lkGAMgASgJEg0KBWxpbWl0GAQgASgFEg4KBm9mZnNldBgFIAEoBSJhCh1MaXN0SW5nZXN0aW9uRmFpbHVyZXNSZXNwb25zZRIxCghmYWlsdXJlcxgBIAMoCzIfLmJsb2NrbmluamEudjEuSW5nZXN0aW9uRmFpbHVyZRINCgV0b3RhbBgCIAEoBSIoChpHZXRJbmdlc3Rpb25GYWlsdXJlUmVxdWVzdBIKCgJpZBgBIAEoCSJPChtHZXRJbmdlc3Rpb25GYWlsdXJlUmVzcG9uc2USMAoHZmFpbHVyZRgBIAEoCzIfLmJsb2NrbmluamEudjEuSW5nZXN0aW9uRmFpbHVyZSJDChdSZXBsYXlGYWlsZWRSb3dzUmVxdWVzdBITCgtwaXBlbGluZV9pZBgBIAEoCRITCgtmYWlsdXJlX2lkcxgCIAMoCSJSChhSZXBsYXlGYWlsZWRSb3dzUmVzcG9uc2USDgoGcnVuX2lkGAEgASgJEhAKCHJlcGxheWVkGAIgASgFEhQKDHN0aWxsX2ZhaWxlZBgDIAEoBSIuChdEZWxldGVGYWlsZWRSb3dzUmVxdWVzdBITCgtmYWlsdXJlX2lkcxgBIAMoCSIrChhEZWxldGVGYWlsZWRSb3dzUmVzcG9uc2USDwoHZGVsZXRlZBgBIAEoBSqdAQoNSW5nZXN0aW9uVHlwZRIeChpJTkdFU1RJT05fVFlQRV9VTlNQRUNJRklFRBAAEhkKFUlOR0VTVElPTl9UWVBFX01BTlVBTBABEhoKFklOR0VTVElPTl9UWVBFX1dFQkhPT0sQAhIWChJJTkdFU1RJT05fVFlQRV9BUEkQAxIdChlJTkdFU1RJT05fVFlQRV9EQVRBX01PREVMEAQqvgIKEUluZ2VzdGlvblNjaGVkdWxlEiIKHklOR0VTVElPTl9TQ0hFRFVMRV9VTlNQRUNJRklFRBAAEh0KGUlOR0VTVElPTl9TQ0hFRFVMRV9NQU5VQUwQARIbChdJTkdFU1RJT05fU0NIRURVTEVfNU1JThACEhwKGElOR0VTVElPTl9TQ0hFRFVMRV8xNU1JThADEhwKGElOR0VTVElPTl9TQ0hFRFVMRV8zME1JThAEEhoKFklOR0VTVElPTl9TQ0hFRFVMRV8xSFIQBRIaChZJTkdFU1RJT05fU0NIRURVTEVfMkhSEAYSGgoWSU5HRVNUSU9OX1NDSEVEVUxFXzZIUhAHEhsKF0lOR0VTVElPTl9TQ0hFRFVMRV8xMkhSEAgSHAoYSU5HRVNUSU9OX1NDSEVEVUxFX0RBSUxZEAkqxQEKC0FQSUF1dGhUeXBlEh0KGUFQSV9BVVRIX1RZUEVfVU5TUEVDSUZJRUQQABIWChJBUElfQVVUSF9UWVBFX05PTkUQARIgChxBUElfQVVUSF9UWVBFX0FQSV9LRVlfSEVBREVSEAISHwobQVBJX0FVVEhfVFlQRV9BUElfS0VZX1FVRVJZEAMSHgoaQVBJX0FVVEhfVFlQRV9CRUFSRVJfVE9LRU4QBBIcChhBUElfQVVUSF9UWVBFX0JBU0lDX0FVVEgQBTKwFQoQSW5nZXN0aW9uU2VydmljZRJ1ChZMaXN0SW5nZXN0aW9uUGlwZWxpbmVzEiwuYmxvY2tuaW5qYS52MS5MaXN0SW5nZXN0aW9uUGlwZWxpbmVzUmVxdWVzdBotLmJsb2NrbmluamEudjEuTGlzdEluZ2VzdGlvblBpcGVsaW5lc1Jlc3BvbnNlEm8KFEdldEluZ2VzdGlvblBpcGVsaW5lEiouYmxvY2tuaW5qYS52MS5HZXRJbmdlc3Rpb25QaXBlbGluZVJlcXVlc3QaKy5ibG9ja25pbmphLnYxLkdldEluZ2VzdGlvblBpcGVsaW5lUmVzcG9uc2USeAoXQ3JlYXRlSW5nZXN0aW9uUGlwZWxpbmUSLS5ibG9ja25pbmphLnYxLkNyZWF0ZUluZ2VzdGlvblBpcGVsaW5lUmVxdWVzdBouLmJsb2NrbmluamEudjEuQ3JlYXRlSW5nZXN0aW9uUGlwZWxpbmVSZXNwb25zZRJ4ChdVcGRhdGVJbmdlc3Rpb25QaXBlbGluZRItLmJsb2NrbmluamEudjEuVXBkYXRlSW5nZXN0aW9uUGlwZWxpbmVSZXF1ZXN0Gi4uYmxvY2tuaW5qYS52MS5VcGRhdGVJbmdlc3Rpb25QaXBlbGluZVJlc3BvbnNlEngKF0RlbGV0ZUluZ2VzdGlvblBpcGVsaW5lEi0uYmxvY2tuaW5qYS52MS5EZWxldGVJbmdlc3Rpb25QaXBlbGluZVJlcXVlc3QaLi5ibG9ja25pbmphLnYxLkRlbGV0ZUluZ2VzdGlvblBpcGVsaW5lUmVzcG9uc2USYAoPUnVuSW5nZXN0aW9uTm93EiUuYmxvY2tuaW5qYS52MS5SdW5Jbmdlc3Rpb25Ob3dSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5SdW5Jbmdlc3Rpb25Ob3dSZXNwb25zZRJjChBQcmV2aWV3SW5nZXN0aW9uEiYuYmxvY2tuaW5qYS52MS5QcmV2aWV3SW5nZXN0aW9uUmVxdWVzdBonLmJsb2NrbmluamEudjEuUHJldmlld0luZ2VzdGlvblJlc3BvbnNlEmYKEUxpc3RJbmdlc3Rpb25SdW5zEicuYmxvY2tuaW5qYS52MS5MaXN0SW5nZXN0aW9uUnVuc1JlcXVlc3QaKC5ibG9ja25pbmphLnYxLkxpc3RJbmdlc3Rpb25SdW5zUmVzcG9uc2USYAoPR2V0SW5nZXN0aW9uUnVuEiUuYmxvY2tuaW5qYS52MS5HZXRJbmdlc3Rpb25SdW5SZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5HZXRJbmdlc3Rpb25SdW5SZXNwb25zZRJmChFHZXRJbmdlc3Rpb25TdGF0cxInLmJsb2NrbmluamEudjEuR2V0SW5nZXN0aW9uU3RhdHNSZXF1ZXN0GiguYmxvY2tuaW5qYS52MS5HZXRJbmdlc3Rpb25TdGF0c1Jlc3BvbnNlEngKF1JlZ2VuZXJhdGVXZWJob29rU2VjcmV0Ei0uYmxvY2tuaW5qYS52MS5SZWdlbmVyYXRlV2ViaG9va1NlY3JldFJlcXVlc3QaLi5ibG9ja25pbmphLnYxLlJlZ2VuZXJhdGVXZWJob29rU2VjcmV0UmVzcG9uc2USaQoSR2V0V2ViaG9va0VuZHBvaW50EiguYmxvY2tuaW5qYS52MS5HZXRXZWJob29rRW5kcG9pbnRSZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5HZXRXZWJob29rRW5kcG9pbnRSZXNwb25zZRJmChFUZXN0QVBJQ29ubmVjdGlvbhInLmJsb2NrbmluamEudjEuVGVzdEFQSUNvbm5lY3Rpb25SZXF1ZXN0GiguYmxvY2tuaW5qYS52MS5UZXN0QVBJQ29ubmVjdGlvblJlc3BvbnNlEmkKElJlc2V0UGlwZWxpbmVTdGF0ZRIoLmJsb2NrbmluamEudjEuUmVzZXRQaXBlbGluZVN0YXRlUmVxdWVzdBopLmJsb2NrbmluamEudjEuUmVzZXRQaXBlbGluZVN0YXRlUmVzcG9uc2USYAoPVGVzdFRyYW5zbGF0aW9uEiUuYmxvY2tuaW5qYS52MS5UZXN0VHJhbnNsYXRpb25SZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5UZXN0VHJhbnNsYXRpb25SZXNwb25zZRJvChRTYXZlRHJhZnRUcmFuc2xhdGlvbhIqLmJsb2NrbmluamEudjEuU2F2ZURyYWZ0VHJhbnNsYXRpb25SZXF1ZXN0GisuYmxvY2tuaW5qYS52MS5TYXZlRHJhZnRUcmFuc2xhdGlvblJlc3BvbnNlEmkKElB1Ymxpc2hUcmFuc2xhdGlvbhIoLmJsb2NrbmluamEudjEuUHVibGlzaFRyYW5zbGF0aW9uUmVxdWVzdBopLmJsb2NrbmluamEudjEuUHVibGlzaFRyYW5zbGF0aW9uUmVzcG9uc2USVwoMRGlzY2FyZERyYWZ0EiIuYmxvY2tuaW5qYS52MS5EaXNjYXJkRHJhZnRSZXF1ZXN0GiMuYmxvY2tuaW5qYS52MS5EaXNjYXJkRHJhZnRSZXNwb25zZRJjChBBY3RpdmF0ZVBpcGVsaW5lEiYuYmxvY2tuaW5qYS52MS5BY3RpdmF0ZVBpcGVsaW5lUmVxdWVzdBonLmJsb2NrbmluamEudjEuQWN0aXZhdGVQaXBlbGluZVJlc3BvbnNlEmkKEkRlYWN0aXZhdGVQaXBlbGluZRIoLmJsb2NrbmluamEudjEuRGVhY3RpdmF0ZVBpcGVsaW5lUmVxdWVzdBopLmJsb2NrbmluamEudjEuRGVhY3RpdmF0ZVBpcGVsaW5lUmVzcG9uc2USgQEKGkdldFBpcGVsaW5lQWN0aXZhdGlvblN0YXRlEjAuYmxvY2tuaW5qYS52MS5HZXRQaXBlbGluZUFjdGl2YXRpb25TdGF0ZVJlcXVlc3QaMS5ibG9ja25pbmphLnYxLkdldFBpcGVsaW5lQWN0aXZhdGlvblN0YXRlUmVzcG9uc2UScgoVTGlzdEluZ2VzdGlvbkZhaWx1cmVzEisuYmxvY2tuaW5qYS52MS5MaXN0SW5nZXN0aW9uRmFpbHVyZXNSZXF1ZXN0GiwuYmxvY2tuaW5qYS52MS5MaXN0SW5nZXN0aW9uRmFpbHVyZXNSZXNwb25zZRJsChNHZXRJbmdlc3Rpb25GYWlsdXJlEikuYmxvY2tuaW5qYS52MS5HZXRJbmdlc3Rpb25GYWlsdXJlUmVxdWVzdBoqLmJsb2NrbmluamEudjEuR2V0SW5nZXN0aW9uRmFpbHVyZVJlc3BvbnNlEmMKEFJlcGxheUZhaWxlZFJvd3MSJi5ibG9ja25pbmphLnYxLlJlcGxheUZhaWxlZFJvd3NSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5SZXBsYXlGYWlsZWRSb3dzUmVzcG9uc2USYwoQRGVsZXRlRmFpbGVkUm93cxImLmJsb2NrbmluamEudjEuRGVsZXRlRmFpbGVkUm93c1JlcXVlc3QaJy5ibG9ja25pbmphLnYxLkRlbGV0ZUZhaWxlZFJvd3NSZXNwb25zZULEAQoRY29tLmJsb2NrbmluamEudjFCDkluZ2VzdGlvblByb3RvUAFaSmdpdC5kZXYuYWxleGR1bm1vdy5jb20vYmxvY2svbmluamEvaW50ZXJuYWwvYXBpL2Jsb2NrbmluamEvdjE7YmxvY2tuaW5qYXYxogIDQlhYqgINQmxvY2tuaW5qYS5WMcoCDUJsb2NrbmluamFcVjHiAhlCbG9ja25pbmphXFYxXEdQQk1ldGFkYXRh6gIOQmxvY2tuaW5qYTo6VjFiBnByb3RvMw", + [file_google_protobuf_struct, file_google_protobuf_timestamp], +); +/** + * @generated from enum blockninja.v1.IngestionType + */ +var IngestionType; +(function (IngestionType) { + /** + * @generated from enum value: INGESTION_TYPE_UNSPECIFIED = 0; + */ + IngestionType[(IngestionType["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * No automation, rows added via UI + * + * @generated from enum value: INGESTION_TYPE_MANUAL = 1; + */ + IngestionType[(IngestionType["MANUAL"] = 1)] = "MANUAL"; + /** + * External systems push data + * + * @generated from enum value: INGESTION_TYPE_WEBHOOK = 2; + */ + IngestionType[(IngestionType["WEBHOOK"] = 2)] = "WEBHOOK"; + /** + * Pull from external APIs + * + * @generated from enum value: INGESTION_TYPE_API = 3; + */ + IngestionType[(IngestionType["API"] = 3)] = "API"; + /** + * Compose from internal datasources + * + * @generated from enum value: INGESTION_TYPE_DATA_MODEL = 4; + */ + IngestionType[(IngestionType["DATA_MODEL"] = 4)] = "DATA_MODEL"; +})(IngestionType || (IngestionType = {})); +/** + * @generated from enum blockninja.v1.IngestionSchedule + */ +var IngestionSchedule; +(function (IngestionSchedule) { + /** + * @generated from enum value: INGESTION_SCHEDULE_UNSPECIFIED = 0; + */ + IngestionSchedule[(IngestionSchedule["INGESTION_SCHEDULE_UNSPECIFIED"] = 0)] = "INGESTION_SCHEDULE_UNSPECIFIED"; + /** + * @generated from enum value: INGESTION_SCHEDULE_MANUAL = 1; + */ + IngestionSchedule[(IngestionSchedule["INGESTION_SCHEDULE_MANUAL"] = 1)] = "INGESTION_SCHEDULE_MANUAL"; + /** + * @generated from enum value: INGESTION_SCHEDULE_5MIN = 2; + */ + IngestionSchedule[(IngestionSchedule["INGESTION_SCHEDULE_5MIN"] = 2)] = "INGESTION_SCHEDULE_5MIN"; + /** + * @generated from enum value: INGESTION_SCHEDULE_15MIN = 3; + */ + IngestionSchedule[(IngestionSchedule["INGESTION_SCHEDULE_15MIN"] = 3)] = "INGESTION_SCHEDULE_15MIN"; + /** + * @generated from enum value: INGESTION_SCHEDULE_30MIN = 4; + */ + IngestionSchedule[(IngestionSchedule["INGESTION_SCHEDULE_30MIN"] = 4)] = "INGESTION_SCHEDULE_30MIN"; + /** + * @generated from enum value: INGESTION_SCHEDULE_1HR = 5; + */ + IngestionSchedule[(IngestionSchedule["INGESTION_SCHEDULE_1HR"] = 5)] = "INGESTION_SCHEDULE_1HR"; + /** + * @generated from enum value: INGESTION_SCHEDULE_2HR = 6; + */ + IngestionSchedule[(IngestionSchedule["INGESTION_SCHEDULE_2HR"] = 6)] = "INGESTION_SCHEDULE_2HR"; + /** + * @generated from enum value: INGESTION_SCHEDULE_6HR = 7; + */ + IngestionSchedule[(IngestionSchedule["INGESTION_SCHEDULE_6HR"] = 7)] = "INGESTION_SCHEDULE_6HR"; + /** + * @generated from enum value: INGESTION_SCHEDULE_12HR = 8; + */ + IngestionSchedule[(IngestionSchedule["INGESTION_SCHEDULE_12HR"] = 8)] = "INGESTION_SCHEDULE_12HR"; + /** + * @generated from enum value: INGESTION_SCHEDULE_DAILY = 9; + */ + IngestionSchedule[(IngestionSchedule["INGESTION_SCHEDULE_DAILY"] = 9)] = "INGESTION_SCHEDULE_DAILY"; +})(IngestionSchedule || (IngestionSchedule = {})); +/** + * @generated from enum blockninja.v1.APIAuthType + */ +var APIAuthType; +(function (APIAuthType) { + /** + * @generated from enum value: API_AUTH_TYPE_UNSPECIFIED = 0; + */ + APIAuthType[(APIAuthType["API_AUTH_TYPE_UNSPECIFIED"] = 0)] = "API_AUTH_TYPE_UNSPECIFIED"; + /** + * @generated from enum value: API_AUTH_TYPE_NONE = 1; + */ + APIAuthType[(APIAuthType["API_AUTH_TYPE_NONE"] = 1)] = "API_AUTH_TYPE_NONE"; + /** + * @generated from enum value: API_AUTH_TYPE_API_KEY_HEADER = 2; + */ + APIAuthType[(APIAuthType["API_AUTH_TYPE_API_KEY_HEADER"] = 2)] = "API_AUTH_TYPE_API_KEY_HEADER"; + /** + * @generated from enum value: API_AUTH_TYPE_API_KEY_QUERY = 3; + */ + APIAuthType[(APIAuthType["API_AUTH_TYPE_API_KEY_QUERY"] = 3)] = "API_AUTH_TYPE_API_KEY_QUERY"; + /** + * @generated from enum value: API_AUTH_TYPE_BEARER_TOKEN = 4; + */ + APIAuthType[(APIAuthType["API_AUTH_TYPE_BEARER_TOKEN"] = 4)] = "API_AUTH_TYPE_BEARER_TOKEN"; + /** + * @generated from enum value: API_AUTH_TYPE_BASIC_AUTH = 5; + */ + APIAuthType[(APIAuthType["API_AUTH_TYPE_BASIC_AUTH"] = 5)] = "API_AUTH_TYPE_BASIC_AUTH"; +})(APIAuthType || (APIAuthType = {})); +/** + * IngestionService manages ingestion pipelines that populate data tables. + * Supports webhook (push), API (pull), and data model (internal compose) ingestion types. + * + * @generated from service blockninja.v1.IngestionService + */ +const IngestionService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_ingestion, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/ingestion.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Pipeline CRUD + * + * @generated from rpc blockninja.v1.IngestionService.ListIngestionPipelines + */ +IngestionService.method.listIngestionPipelines; +/** + * @generated from rpc blockninja.v1.IngestionService.GetIngestionPipeline + */ +IngestionService.method.getIngestionPipeline; +/** + * @generated from rpc blockninja.v1.IngestionService.CreateIngestionPipeline + */ +IngestionService.method.createIngestionPipeline; +/** + * @generated from rpc blockninja.v1.IngestionService.UpdateIngestionPipeline + */ +IngestionService.method.updateIngestionPipeline; +/** + * @generated from rpc blockninja.v1.IngestionService.DeleteIngestionPipeline + */ +IngestionService.method.deleteIngestionPipeline; +/** + * Execution + * + * @generated from rpc blockninja.v1.IngestionService.RunIngestionNow + */ +IngestionService.method.runIngestionNow; +/** + * @generated from rpc blockninja.v1.IngestionService.PreviewIngestion + */ +IngestionService.method.previewIngestion; +/** + * Run history + * + * @generated from rpc blockninja.v1.IngestionService.ListIngestionRuns + */ +IngestionService.method.listIngestionRuns; +/** + * @generated from rpc blockninja.v1.IngestionService.GetIngestionRun + */ +IngestionService.method.getIngestionRun; +/** + * @generated from rpc blockninja.v1.IngestionService.GetIngestionStats + */ +IngestionService.method.getIngestionStats; +/** + * Webhook-specific + * + * @generated from rpc blockninja.v1.IngestionService.RegenerateWebhookSecret + */ +IngestionService.method.regenerateWebhookSecret; +/** + * @generated from rpc blockninja.v1.IngestionService.GetWebhookEndpoint + */ +IngestionService.method.getWebhookEndpoint; +/** + * API-specific + * + * @generated from rpc blockninja.v1.IngestionService.TestAPIConnection + */ +IngestionService.method.testAPIConnection; +/** + * State management + * + * @generated from rpc blockninja.v1.IngestionService.ResetPipelineState + */ +IngestionService.method.resetPipelineState; +/** + * Translation workflow (draft/publish/test) + * + * @generated from rpc blockninja.v1.IngestionService.TestTranslation + */ +IngestionService.method.testTranslation; +/** + * @generated from rpc blockninja.v1.IngestionService.SaveDraftTranslation + */ +IngestionService.method.saveDraftTranslation; +/** + * @generated from rpc blockninja.v1.IngestionService.PublishTranslation + */ +IngestionService.method.publishTranslation; +/** + * @generated from rpc blockninja.v1.IngestionService.DiscardDraft + */ +IngestionService.method.discardDraft; +/** + * Pipeline activation (separate from enabled) + * + * @generated from rpc blockninja.v1.IngestionService.ActivatePipeline + */ +IngestionService.method.activatePipeline; +/** + * @generated from rpc blockninja.v1.IngestionService.DeactivatePipeline + */ +IngestionService.method.deactivatePipeline; +/** + * @generated from rpc blockninja.v1.IngestionService.GetPipelineActivationState + */ +IngestionService.method.getPipelineActivationState; +/** + * Failure management + * + * @generated from rpc blockninja.v1.IngestionService.ListIngestionFailures + */ +IngestionService.method.listIngestionFailures; +/** + * @generated from rpc blockninja.v1.IngestionService.GetIngestionFailure + */ +IngestionService.method.getIngestionFailure; +/** + * @generated from rpc blockninja.v1.IngestionService.ReplayFailedRows + */ +IngestionService.method.replayFailedRows; +/** + * @generated from rpc blockninja.v1.IngestionService.DeleteFailedRows + */ +IngestionService.method.deleteFailedRows; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/mailing_lists.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/mailing_lists.proto. + */ +const file_blockninja_v1_mailing_lists = /*@__PURE__*/ fileDesc( + "CiFibG9ja25pbmphL3YxL21haWxpbmdfbGlzdHMucHJvdG8SDWJsb2NrbmluamEudjEi/wUKC01haWxpbmdMaXN0EgoKAmlkGAEgASgJEhUKDWRhdGFfdGFibGVfaWQYAiABKAkSDAoEc2x1ZxgDIAEoCRIMCgRuYW1lGAQgASgJEhgKC2Rlc2NyaXB0aW9uGAUgASgJSACIAQESFQoNZG91YmxlX29wdF9pbhgGIAEoCBIlChhjb25maXJtYXRpb25fdGVtcGxhdGVfaWQYByABKAlIAYgBARIgChN3ZWxjb21lX3RlbXBsYXRlX2lkGAggASgJSAKIAQESJAoXdW5zdWJzY3JpYmVfdGVtcGxhdGVfaWQYCSABKAlIA4gBARIlChhjb25maXJtYXRpb25fd29ya2Zsb3dfaWQYCiABKAlIBIgBARIgChN3ZWxjb21lX3dvcmtmbG93X2lkGAsgASgJSAWIAQESGQoRYWxsb3dfcmVzdWJzY3JpYmUYDCABKAgSGwoOZGVmYXVsdF9zb3VyY2UYDSABKAlIBogBARInCgZzY2hlbWEYDiABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0EhgKEHN1YnNjcmliZXJfY291bnQYDyABKAMSFAoMYWN0aXZlX2NvdW50GBAgASgDEhUKDXBlbmRpbmdfY291bnQYESABKAMSLgoKY3JlYXRlZF9hdBgSIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKdXBkYXRlZF9hdBgTIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFwoPZGF0YV90YWJsZV9uYW1lGBQgASgJQg4KDF9kZXNjcmlwdGlvbkIbChlfY29uZmlybWF0aW9uX3RlbXBsYXRlX2lkQhYKFF93ZWxjb21lX3RlbXBsYXRlX2lkQhoKGF91bnN1YnNjcmliZV90ZW1wbGF0ZV9pZEIbChlfY29uZmlybWF0aW9uX3dvcmtmbG93X2lkQhYKFF93ZWxjb21lX3dvcmtmbG93X2lkQhEKD19kZWZhdWx0X3NvdXJjZSLtBgoKU3Vic2NyaWJlchIKCgJpZBgBIAEoCRINCgVlbWFpbBgCIAEoCRIRCgRuYW1lGAMgASgJSACIAQESFwoKZmlyc3RfbmFtZRgEIAEoCUgBiAEBEhYKCWxhc3RfbmFtZRgFIAEoCUgCiAEBEg4KBnN0YXR1cxgGIAEoCRITCgZzb3VyY2UYByABKAlIA4gBARIuCg1jdXN0b21fZmllbGRzGAggASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBI2Cg1zdWJzY3JpYmVkX2F0GAkgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgEiAEBEjUKDGNvbmZpcm1lZF9hdBgKIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIBYgBARI4Cg91bnN1YnNjcmliZWRfYXQYCyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSAaIAQESLgoKY3JlYXRlZF9hdBgMIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKdXBkYXRlZF9hdBgNIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASHgoRZW1haWxfb3BlbnNfY291bnQYDiABKAVIB4gBARIfChJlbWFpbF9jbGlja3NfY291bnQYDyABKAVICIgBARI3Cg5sYXN0X29wZW5lZF9hdBgQIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBICYgBARI4Cg9sYXN0X2NsaWNrZWRfYXQYESABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSAqIAQESHQoQZW5nYWdlbWVudF9zY29yZRgSIAEoBUgLiAEBQgcKBV9uYW1lQg0KC19maXJzdF9uYW1lQgwKCl9sYXN0X25hbWVCCQoHX3NvdXJjZUIQCg5fc3Vic2NyaWJlZF9hdEIPCg1fY29uZmlybWVkX2F0QhIKEF91bnN1YnNjcmliZWRfYXRCFAoSX2VtYWlsX29wZW5zX2NvdW50QhUKE19lbWFpbF9jbGlja3NfY291bnRCEQoPX2xhc3Rfb3BlbmVkX2F0QhIKEF9sYXN0X2NsaWNrZWRfYXRCEwoRX2VuZ2FnZW1lbnRfc2NvcmUiswUKCENhbXBhaWduEgoKAmlkGAEgASgJEhcKD21haWxpbmdfbGlzdF9pZBgCIAEoCRIRCglsaXN0X3NsdWcYAyABKAkSEQoJbGlzdF9uYW1lGAQgASgJEgwKBG5hbWUYBSABKAkSEwoLdGVtcGxhdGVfaWQYBiABKAkSDwoHc3ViamVjdBgHIAEoCRIOCgZzdGF0dXMYCCABKAkSNQoMc2NoZWR1bGVkX2F0GAkgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgAiAEBEjMKCnN0YXJ0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSAGIAQESNQoMY29tcGxldGVkX2F0GAsgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgCiAEBEhgKEHRvdGFsX3JlY2lwaWVudHMYDCABKAUSEgoKc2VudF9jb3VudBgNIAEoBRIUCgxmYWlsZWRfY291bnQYDiABKAUSFAoMb3BlbmVkX2NvdW50GA8gASgFEhUKDWNsaWNrZWRfY291bnQYECABKAUSKgoJdmFyaWFibGVzGBEgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBI0Cg5zZWdtZW50X2ZpbHRlchgSIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3RIA4gBARIuCgpjcmVhdGVkX2F0GBMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GBQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEIPCg1fc2NoZWR1bGVkX2F0Qg0KC19zdGFydGVkX2F0Qg8KDV9jb21wbGV0ZWRfYXRCEQoPX3NlZ21lbnRfZmlsdGVyIq4DCgxEcmlwU2VxdWVuY2USCgoCaWQYASABKAkSFwoPbWFpbGluZ19saXN0X2lkGAIgASgJEhEKCWxpc3Rfc2x1ZxgDIAEoCRIRCglsaXN0X25hbWUYBCABKAkSDAoEbmFtZRgFIAEoCRIYCgtkZXNjcmlwdGlvbhgGIAEoCUgAiAEBEhUKDXRyaWdnZXJfZXZlbnQYByABKAkSMwoSdHJpZ2dlcl9jb25kaXRpb25zGAggASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIPCgdlbmFibGVkGAkgASgIEhIKCnN0ZXBfY291bnQYCiABKAUSGgoSYWN0aXZlX2Vucm9sbG1lbnRzGAsgASgFEi4KBXN0ZXBzGAwgAygLMh8uYmxvY2tuaW5qYS52MS5EcmlwU2VxdWVuY2VTdGVwEi4KCmNyZWF0ZWRfYXQYDSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYDiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQg4KDF9kZXNjcmlwdGlvbiL4AgoQRHJpcFNlcXVlbmNlU3RlcBIKCgJpZBgBIAEoCRITCgtzZXF1ZW5jZV9pZBgCIAEoCRISCgpzdGVwX29yZGVyGAMgASgFEhIKCmRlbGF5X2RheXMYBCABKAUSEwoLZGVsYXlfaG91cnMYBSABKAUSFQoNZGVsYXlfbWludXRlcxgGIAEoBRITCgt0ZW1wbGF0ZV9pZBgHIAEoCRIVCg10ZW1wbGF0ZV9uYW1lGAggASgJEh0KEHRlbXBsYXRlX3N1YmplY3QYCSABKAlIAIgBARIdChBzdWJqZWN0X292ZXJyaWRlGAogASgJSAGIAQESKwoKY29uZGl0aW9ucxgLIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSLgoKY3JlYXRlZF9hdBgMIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCEwoRX3RlbXBsYXRlX3N1YmplY3RCEwoRX3N1YmplY3Rfb3ZlcnJpZGUi6AIKDkRyaXBFbnJvbGxtZW50EgoKAmlkGAEgASgJEhMKC3NlcXVlbmNlX2lkGAIgASgJEhgKEHN1YnNjcmliZXJfZW1haWwYAyABKAkSHgoRc3Vic2NyaWJlcl9yb3dfaWQYBCABKAlIAIgBARIUCgxjdXJyZW50X3N0ZXAYBSABKAUSLwoLZW5yb2xsZWRfYXQYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjUKDGxhc3Rfc3RlcF9hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIAYgBARI1CgxuZXh0X3N0ZXBfYXQYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSAKIAQESDgoGc3RhdHVzGAkgASgJQhQKEl9zdWJzY3JpYmVyX3Jvd19pZEIPCg1fbGFzdF9zdGVwX2F0Qg8KDV9uZXh0X3N0ZXBfYXQivAMKC1JTU0NhbXBhaWduEgoKAmlkGAEgASgJEhcKD21haWxpbmdfbGlzdF9pZBgCIAEoCRIRCglsaXN0X3NsdWcYAyABKAkSEQoJbGlzdF9uYW1lGAQgASgJEgwKBG5hbWUYBSABKAkSEwoLdGVtcGxhdGVfaWQYBiABKAkSEQoJZnJlcXVlbmN5GAcgASgJEhUKCHNlbmRfZGF5GAggASgFSACIAQESEQoJc2VuZF9ob3VyGAkgASgFEhAKCHRpbWV6b25lGAogASgJEhkKDGxhc3RfcG9zdF9pZBgLIAEoCUgBiAEBEjUKDGxhc3Rfc2VudF9hdBgMIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIAogBARIPCgdlbmFibGVkGA0gASgIEi4KCmNyZWF0ZWRfYXQYDiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYDyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgsKCV9zZW5kX2RheUIPCg1fbGFzdF9wb3N0X2lkQg8KDV9sYXN0X3NlbnRfYXQi4wIKDVRyYWNraW5nRXZlbnQSCgoCaWQYASABKAkSFQoIcXVldWVfaWQYAiABKAlIAIgBARIYCgtjYW1wYWlnbl9pZBgDIAEoCUgBiAEBEhgKEHN1YnNjcmliZXJfZW1haWwYBCABKAkSEgoKZXZlbnRfdHlwZRgFIAEoCRIVCghsaW5rX3VybBgGIAEoCUgCiAEBEhcKCmxpbmtfaW5kZXgYByABKAVIA4gBARIXCgp1c2VyX2FnZW50GAggASgJSASIAQESFwoKaXBfYWRkcmVzcxgJIAEoCUgFiAEBEi4KCmNyZWF0ZWRfYXQYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgsKCV9xdWV1ZV9pZEIOCgxfY2FtcGFpZ25faWRCCwoJX2xpbmtfdXJsQg0KC19saW5rX2luZGV4Qg0KC191c2VyX2FnZW50Qg0KC19pcF9hZGRyZXNzItgDChhDcmVhdGVNYWlsaW5nTGlzdFJlcXVlc3QSDAoEbmFtZRgBIAEoCRIMCgRzbHVnGAIgASgJEhgKC2Rlc2NyaXB0aW9uGAMgASgJSACIAQESFQoNZG91YmxlX29wdF9pbhgEIAEoCBIlChhjb25maXJtYXRpb25fdGVtcGxhdGVfaWQYBSABKAlIAYgBARIgChN3ZWxjb21lX3RlbXBsYXRlX2lkGAYgASgJSAKIAQESJAoXdW5zdWJzY3JpYmVfdGVtcGxhdGVfaWQYByABKAlIA4gBARIZChFhbGxvd19yZXN1YnNjcmliZRgIIAEoCBIbCg5kZWZhdWx0X3NvdXJjZRgJIAEoCUgEiAEBEjUKDWN1c3RvbV9maWVsZHMYCiADKAsyHi5ibG9ja25pbmphLnYxLkZpZWxkRGVmaW5pdGlvbhIdChVhdXRvX2NyZWF0ZV93b3JrZmxvd3MYCyABKAhCDgoMX2Rlc2NyaXB0aW9uQhsKGV9jb25maXJtYXRpb25fdGVtcGxhdGVfaWRCFgoUX3dlbGNvbWVfdGVtcGxhdGVfaWRCGgoYX3Vuc3Vic2NyaWJlX3RlbXBsYXRlX2lkQhEKD19kZWZhdWx0X3NvdXJjZSJNChlDcmVhdGVNYWlsaW5nTGlzdFJlc3BvbnNlEjAKDG1haWxpbmdfbGlzdBgBIAEoCzIaLmJsb2NrbmluamEudjEuTWFpbGluZ0xpc3QiIwoVR2V0TWFpbGluZ0xpc3RSZXF1ZXN0EgoKAmlkGAEgASgJIkoKFkdldE1haWxpbmdMaXN0UmVzcG9uc2USMAoMbWFpbGluZ19saXN0GAEgASgLMhouYmxvY2tuaW5qYS52MS5NYWlsaW5nTGlzdCIrChtHZXRNYWlsaW5nTGlzdEJ5U2x1Z1JlcXVlc3QSDAoEc2x1ZxgBIAEoCSJQChxHZXRNYWlsaW5nTGlzdEJ5U2x1Z1Jlc3BvbnNlEjAKDG1haWxpbmdfbGlzdBgBIAEoCzIaLmJsb2NrbmluamEudjEuTWFpbGluZ0xpc3QiGQoXTGlzdE1haWxpbmdMaXN0c1JlcXVlc3QiTQoYTGlzdE1haWxpbmdMaXN0c1Jlc3BvbnNlEjEKDW1haWxpbmdfbGlzdHMYASADKAsyGi5ibG9ja25pbmphLnYxLk1haWxpbmdMaXN0ItwDChhVcGRhdGVNYWlsaW5nTGlzdFJlcXVlc3QSCgoCaWQYASABKAkSEQoEc2x1ZxgCIAEoCUgAiAEBEhEKBG5hbWUYAyABKAlIAYgBARIYCgtkZXNjcmlwdGlvbhgEIAEoCUgCiAEBEhoKDWRvdWJsZV9vcHRfaW4YBSABKAhIA4gBARIlChhjb25maXJtYXRpb25fdGVtcGxhdGVfaWQYBiABKAlIBIgBARIgChN3ZWxjb21lX3RlbXBsYXRlX2lkGAcgASgJSAWIAQESJAoXdW5zdWJzY3JpYmVfdGVtcGxhdGVfaWQYCCABKAlIBogBARIeChFhbGxvd19yZXN1YnNjcmliZRgJIAEoCEgHiAEBEhsKDmRlZmF1bHRfc291cmNlGAogASgJSAiIAQFCBwoFX3NsdWdCBwoFX25hbWVCDgoMX2Rlc2NyaXB0aW9uQhAKDl9kb3VibGVfb3B0X2luQhsKGV9jb25maXJtYXRpb25fdGVtcGxhdGVfaWRCFgoUX3dlbGNvbWVfdGVtcGxhdGVfaWRCGgoYX3Vuc3Vic2NyaWJlX3RlbXBsYXRlX2lkQhQKEl9hbGxvd19yZXN1YnNjcmliZUIRCg9fZGVmYXVsdF9zb3VyY2UiTQoZVXBkYXRlTWFpbGluZ0xpc3RSZXNwb25zZRIwCgxtYWlsaW5nX2xpc3QYASABKAsyGi5ibG9ja25pbmphLnYxLk1haWxpbmdMaXN0IiYKGERlbGV0ZU1haWxpbmdMaXN0UmVxdWVzdBIKCgJpZBgBIAEoCSIbChlEZWxldGVNYWlsaW5nTGlzdFJlc3BvbnNlIikKG0dldE1haWxpbmdMaXN0U2NoZW1hUmVxdWVzdBIKCgJpZBgBIAEoCSJOChxHZXRNYWlsaW5nTGlzdFNjaGVtYVJlc3BvbnNlEi4KBmZpZWxkcxgBIAMoCzIeLmJsb2NrbmluamEudjEuRmllbGREZWZpbml0aW9uIlwKHlVwZGF0ZU1haWxpbmdMaXN0U2NoZW1hUmVxdWVzdBIKCgJpZBgBIAEoCRIuCgZmaWVsZHMYAiADKAsyHi5ibG9ja25pbmphLnYxLkZpZWxkRGVmaW5pdGlvbiJRCh9VcGRhdGVNYWlsaW5nTGlzdFNjaGVtYVJlc3BvbnNlEi4KBmZpZWxkcxgBIAMoCzIeLmJsb2NrbmluamEudjEuRmllbGREZWZpbml0aW9uIl8KFUFkZEN1c3RvbUZpZWxkUmVxdWVzdBIXCg9tYWlsaW5nX2xpc3RfaWQYASABKAkSLQoFZmllbGQYAiABKAsyHi5ibG9ja25pbmphLnYxLkZpZWxkRGVmaW5pdGlvbiJIChZBZGRDdXN0b21GaWVsZFJlc3BvbnNlEi4KBmZpZWxkcxgBIAMoCzIeLmJsb2NrbmluamEudjEuRmllbGREZWZpbml0aW9uIkYKGFJlbW92ZUN1c3RvbUZpZWxkUmVxdWVzdBIXCg9tYWlsaW5nX2xpc3RfaWQYASABKAkSEQoJZmllbGRfa2V5GAIgASgJIksKGVJlbW92ZUN1c3RvbUZpZWxkUmVzcG9uc2USLgoGZmllbGRzGAEgAygLMh4uYmxvY2tuaW5qYS52MS5GaWVsZERlZmluaXRpb24i6AEKFkxpc3RTdWJzY3JpYmVyc1JlcXVlc3QSFwoPbWFpbGluZ19saXN0X2lkGAEgASgJEhMKBnNlYXJjaBgCIAEoCUgAiAEBEhMKBnN0YXR1cxgDIAEoCUgBiAEBEg0KBWxpbWl0GAQgASgFEg4KBm9mZnNldBgFIAEoBRIXCgpzb3J0X2ZpZWxkGAYgASgJSAKIAQESGwoOc29ydF9kaXJlY3Rpb24YByABKAlIA4gBAUIJCgdfc2VhcmNoQgkKB19zdGF0dXNCDQoLX3NvcnRfZmllbGRCEQoPX3NvcnRfZGlyZWN0aW9uIlgKF0xpc3RTdWJzY3JpYmVyc1Jlc3BvbnNlEi4KC3N1YnNjcmliZXJzGAEgAygLMhkuYmxvY2tuaW5qYS52MS5TdWJzY3JpYmVyEg0KBXRvdGFsGAIgASgDIkYKFEdldFN1YnNjcmliZXJSZXF1ZXN0EhcKD21haWxpbmdfbGlzdF9pZBgBIAEoCRIVCg1zdWJzY3JpYmVyX2lkGAIgASgJIkYKFUdldFN1YnNjcmliZXJSZXNwb25zZRItCgpzdWJzY3JpYmVyGAEgASgLMhkuYmxvY2tuaW5qYS52MS5TdWJzY3JpYmVyIrMCChdDcmVhdGVTdWJzY3JpYmVyUmVxdWVzdBIXCg9tYWlsaW5nX2xpc3RfaWQYASABKAkSDQoFZW1haWwYAiABKAkSEQoEbmFtZRgDIAEoCUgAiAEBEhcKCmZpcnN0X25hbWUYBCABKAlIAYgBARIWCglsYXN0X25hbWUYBSABKAlIAogBARITCgZzdGF0dXMYBiABKAlIA4gBARITCgZzb3VyY2UYByABKAlIBIgBARIuCg1jdXN0b21fZmllbGRzGAggASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIWCg5za2lwX3dvcmtmbG93cxgJIAEoCEIHCgVfbmFtZUINCgtfZmlyc3RfbmFtZUIMCgpfbGFzdF9uYW1lQgkKB19zdGF0dXNCCQoHX3NvdXJjZSJJChhDcmVhdGVTdWJzY3JpYmVyUmVzcG9uc2USLQoKc3Vic2NyaWJlchgBIAEoCzIZLmJsb2NrbmluamEudjEuU3Vic2NyaWJlciKhAgoXVXBkYXRlU3Vic2NyaWJlclJlcXVlc3QSFwoPbWFpbGluZ19saXN0X2lkGAEgASgJEhUKDXN1YnNjcmliZXJfaWQYAiABKAkSEgoFZW1haWwYAyABKAlIAIgBARIRCgRuYW1lGAQgASgJSAGIAQESFwoKZmlyc3RfbmFtZRgFIAEoCUgCiAEBEhYKCWxhc3RfbmFtZRgGIAEoCUgDiAEBEhMKBnN0YXR1cxgHIAEoCUgEiAEBEi4KDWN1c3RvbV9maWVsZHMYCCABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0QggKBl9lbWFpbEIHCgVfbmFtZUINCgtfZmlyc3RfbmFtZUIMCgpfbGFzdF9uYW1lQgkKB19zdGF0dXMiSQoYVXBkYXRlU3Vic2NyaWJlclJlc3BvbnNlEi0KCnN1YnNjcmliZXIYASABKAsyGS5ibG9ja25pbmphLnYxLlN1YnNjcmliZXIiSQoXRGVsZXRlU3Vic2NyaWJlclJlcXVlc3QSFwoPbWFpbGluZ19saXN0X2lkGAEgASgJEhUKDXN1YnNjcmliZXJfaWQYAiABKAkiGgoYRGVsZXRlU3Vic2NyaWJlclJlc3BvbnNlIrcCChhJbXBvcnRTdWJzY3JpYmVyc1JlcXVlc3QSFwoPbWFpbGluZ19saXN0X2lkGAEgASgJEhAKCGNzdl9kYXRhGAIgASgJEhIKCmhhc19oZWFkZXIYAyABKAgSEQoJZGVsaW1pdGVyGAQgASgJElIKDmNvbHVtbl9tYXBwaW5nGAUgAygLMjouYmxvY2tuaW5qYS52MS5JbXBvcnRTdWJzY3JpYmVyc1JlcXVlc3QuQ29sdW1uTWFwcGluZ0VudHJ5EicKBG1vZGUYBiABKA4yGS5ibG9ja25pbmphLnYxLkltcG9ydE1vZGUSFgoOc2tpcF93b3JrZmxvd3MYByABKAgaNAoSQ29sdW1uTWFwcGluZ0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiXwoZSW1wb3J0U3Vic2NyaWJlcnNSZXNwb25zZRIQCghpbXBvcnRlZBgBIAEoBRIPCgd1cGRhdGVkGAIgASgFEg8KB3NraXBwZWQYAyABKAUSDgoGZXJyb3JzGAQgAygJInYKGEV4cG9ydFN1YnNjcmliZXJzUmVxdWVzdBIXCg9tYWlsaW5nX2xpc3RfaWQYASABKAkSEwoGc3RhdHVzGAIgASgJSACIAQESDgoGZmllbGRzGAMgAygJEhEKCWRlbGltaXRlchgEIAEoCUIJCgdfc3RhdHVzIjwKGUV4cG9ydFN1YnNjcmliZXJzUmVzcG9uc2USEAoIY3N2X2RhdGEYASABKAkSDQoFY291bnQYAiABKAUi9AEKFlB1YmxpY1N1YnNjcmliZVJlcXVlc3QSEQoJbGlzdF9zbHVnGAEgASgJEg0KBWVtYWlsGAIgASgJEhEKBG5hbWUYAyABKAlIAIgBARIXCgpmaXJzdF9uYW1lGAQgASgJSAGIAQESFgoJbGFzdF9uYW1lGAUgASgJSAKIAQESEwoGc291cmNlGAYgASgJSAOIAQESLgoNY3VzdG9tX2ZpZWxkcxgHIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3RCBwoFX25hbWVCDQoLX2ZpcnN0X25hbWVCDAoKX2xhc3RfbmFtZUIJCgdfc291cmNlIloKF1B1YmxpY1N1YnNjcmliZVJlc3BvbnNlEg8KB3N1Y2Nlc3MYASABKAgSHQoVcmVxdWlyZXNfY29uZmlybWF0aW9uGAIgASgIEg8KB21lc3NhZ2UYAyABKAkiJQoUUHVibGljQ29uZmlybVJlcXVlc3QSDQoFdG9rZW4YASABKAkiiwEKFVB1YmxpY0NvbmZpcm1SZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEg8KB21lc3NhZ2UYAiABKAkSFgoJbGlzdF9zbHVnGAMgASgJSACIAQESGQoMcmVkaXJlY3RfdXJsGAQgASgJSAGIAQFCDAoKX2xpc3Rfc2x1Z0IPCg1fcmVkaXJlY3RfdXJsIikKGFB1YmxpY1Vuc3Vic2NyaWJlUmVxdWVzdBINCgV0b2tlbhgBIAEoCSI9ChlQdWJsaWNVbnN1YnNjcmliZVJlc3BvbnNlEg8KB3N1Y2Nlc3MYASABKAgSDwoHbWVzc2FnZRgCIAEoCSKNAQoeUHVibGljVXBkYXRlUHJlZmVyZW5jZXNSZXF1ZXN0Eg0KBXRva2VuGAEgASgJEhEKBG5hbWUYAiABKAlIAIgBARIsCgtwcmVmZXJlbmNlcxgDIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSEgoKbGlzdF9zbHVncxgEIAMoCUIHCgVfbmFtZSJDCh9QdWJsaWNVcGRhdGVQcmVmZXJlbmNlc1Jlc3BvbnNlEg8KB3N1Y2Nlc3MYASABKAgSDwoHbWVzc2FnZRgCIAEoCSLZAQoVQ3JlYXRlQ2FtcGFpZ25SZXF1ZXN0EhcKD21haWxpbmdfbGlzdF9pZBgBIAEoCRIMCgRuYW1lGAIgASgJEhMKC3RlbXBsYXRlX2lkGAMgASgJEg8KB3N1YmplY3QYBCABKAkSKgoJdmFyaWFibGVzGAUgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBI0Cg5zZWdtZW50X2ZpbHRlchgGIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3RIAIgBAUIRCg9fc2VnbWVudF9maWx0ZXIiQwoWQ3JlYXRlQ2FtcGFpZ25SZXNwb25zZRIpCghjYW1wYWlnbhgBIAEoCzIXLmJsb2NrbmluamEudjEuQ2FtcGFpZ24iIAoSR2V0Q2FtcGFpZ25SZXF1ZXN0EgoKAmlkGAEgASgJIkAKE0dldENhbXBhaWduUmVzcG9uc2USKQoIY2FtcGFpZ24YASABKAsyFy5ibG9ja25pbmphLnYxLkNhbXBhaWduIk4KFExpc3RDYW1wYWlnbnNSZXF1ZXN0EhcKD21haWxpbmdfbGlzdF9pZBgBIAEoCRINCgVsaW1pdBgCIAEoBRIOCgZvZmZzZXQYAyABKAUiUgoVTGlzdENhbXBhaWduc1Jlc3BvbnNlEioKCWNhbXBhaWducxgBIAMoCzIXLmJsb2NrbmluamEudjEuQ2FtcGFpZ24SDQoFdG90YWwYAiABKAMikwIKFVVwZGF0ZUNhbXBhaWduUmVxdWVzdBIKCgJpZBgBIAEoCRIRCgRuYW1lGAIgASgJSACIAQESGAoLdGVtcGxhdGVfaWQYAyABKAlIAYgBARIUCgdzdWJqZWN0GAQgASgJSAKIAQESLwoJdmFyaWFibGVzGAUgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdEgDiAEBEjQKDnNlZ21lbnRfZmlsdGVyGAYgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdEgEiAEBQgcKBV9uYW1lQg4KDF90ZW1wbGF0ZV9pZEIKCghfc3ViamVjdEIMCgpfdmFyaWFibGVzQhEKD19zZWdtZW50X2ZpbHRlciJDChZVcGRhdGVDYW1wYWlnblJlc3BvbnNlEikKCGNhbXBhaWduGAEgASgLMhcuYmxvY2tuaW5qYS52MS5DYW1wYWlnbiIjChVEZWxldGVDYW1wYWlnblJlcXVlc3QSCgoCaWQYASABKAkiGAoWRGVsZXRlQ2FtcGFpZ25SZXNwb25zZSJXChdTY2hlZHVsZUNhbXBhaWduUmVxdWVzdBIKCgJpZBgBIAEoCRIwCgxzY2hlZHVsZWRfYXQYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIkUKGFNjaGVkdWxlQ2FtcGFpZ25SZXNwb25zZRIpCghjYW1wYWlnbhgBIAEoCzIXLmJsb2NrbmluamEudjEuQ2FtcGFpZ24iIQoTU2VuZENhbXBhaWduUmVxdWVzdBIKCgJpZBgBIAEoCSJXChRTZW5kQ2FtcGFpZ25SZXNwb25zZRIpCghjYW1wYWlnbhgBIAEoCzIXLmJsb2NrbmluamEudjEuQ2FtcGFpZ24SFAoMcXVldWVkX2NvdW50GAIgASgFIiMKFUNhbmNlbENhbXBhaWduUmVxdWVzdBIKCgJpZBgBIAEoCSJDChZDYW5jZWxDYW1wYWlnblJlc3BvbnNlEikKCGNhbXBhaWduGAEgASgLMhcuYmxvY2tuaW5qYS52MS5DYW1wYWlnbiIlChdHZXRDYW1wYWlnblN0YXRzUmVxdWVzdBIKCgJpZBgBIAEoCSKcAgoYR2V0Q2FtcGFpZ25TdGF0c1Jlc3BvbnNlEhgKEHRvdGFsX3JlY2lwaWVudHMYASABKAUSEgoKc2VudF9jb3VudBgCIAEoBRIUCgxmYWlsZWRfY291bnQYAyABKAUSFAoMb3BlbmVkX2NvdW50GAQgASgFEhQKDHVuaXF1ZV9vcGVucxgFIAEoBRIVCg1jbGlja2VkX2NvdW50GAYgASgFEhUKDXVuaXF1ZV9jbGlja3MYByABKAUSDwoHYm91bmNlcxgIIAEoBRIUCgxzcGFtX3JlcG9ydHMYCSABKAUSFAoMdW5zdWJzY3JpYmVzGAogASgFEhEKCW9wZW5fcmF0ZRgLIAEoARISCgpjbGlja19yYXRlGAwgASgBIuUBChlDcmVhdGVEcmlwU2VxdWVuY2VSZXF1ZXN0EhcKD21haWxpbmdfbGlzdF9pZBgBIAEoCRIMCgRuYW1lGAIgASgJEhgKC2Rlc2NyaXB0aW9uGAMgASgJSACIAQESFQoNdHJpZ2dlcl9ldmVudBgEIAEoCRI4ChJ0cmlnZ2VyX2NvbmRpdGlvbnMYBSABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0SAGIAQESDwoHZW5hYmxlZBgGIAEoCEIOCgxfZGVzY3JpcHRpb25CFQoTX3RyaWdnZXJfY29uZGl0aW9ucyJQChpDcmVhdGVEcmlwU2VxdWVuY2VSZXNwb25zZRIyCg1kcmlwX3NlcXVlbmNlGAEgASgLMhsuYmxvY2tuaW5qYS52MS5EcmlwU2VxdWVuY2UiOwoWR2V0RHJpcFNlcXVlbmNlUmVxdWVzdBIKCgJpZBgBIAEoCRIVCg1pbmNsdWRlX3N0ZXBzGAIgASgIIk0KF0dldERyaXBTZXF1ZW5jZVJlc3BvbnNlEjIKDWRyaXBfc2VxdWVuY2UYASABKAsyGy5ibG9ja25pbmphLnYxLkRyaXBTZXF1ZW5jZSIzChhMaXN0RHJpcFNlcXVlbmNlc1JlcXVlc3QSFwoPbWFpbGluZ19saXN0X2lkGAEgASgJIlAKGUxpc3REcmlwU2VxdWVuY2VzUmVzcG9uc2USMwoOZHJpcF9zZXF1ZW5jZXMYASADKAsyGy5ibG9ja25pbmphLnYxLkRyaXBTZXF1ZW5jZSKOAgoZVXBkYXRlRHJpcFNlcXVlbmNlUmVxdWVzdBIKCgJpZBgBIAEoCRIRCgRuYW1lGAIgASgJSACIAQESGAoLZGVzY3JpcHRpb24YAyABKAlIAYgBARIaCg10cmlnZ2VyX2V2ZW50GAQgASgJSAKIAQESOAoSdHJpZ2dlcl9jb25kaXRpb25zGAUgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdEgDiAEBEhQKB2VuYWJsZWQYBiABKAhIBIgBAUIHCgVfbmFtZUIOCgxfZGVzY3JpcHRpb25CEAoOX3RyaWdnZXJfZXZlbnRCFQoTX3RyaWdnZXJfY29uZGl0aW9uc0IKCghfZW5hYmxlZCJQChpVcGRhdGVEcmlwU2VxdWVuY2VSZXNwb25zZRIyCg1kcmlwX3NlcXVlbmNlGAEgASgLMhsuYmxvY2tuaW5qYS52MS5EcmlwU2VxdWVuY2UiJwoZRGVsZXRlRHJpcFNlcXVlbmNlUmVxdWVzdBIKCgJpZBgBIAEoCSIcChpEZWxldGVEcmlwU2VxdWVuY2VSZXNwb25zZSKSAgodQ3JlYXRlRHJpcFNlcXVlbmNlU3RlcFJlcXVlc3QSEwoLc2VxdWVuY2VfaWQYASABKAkSEgoKc3RlcF9vcmRlchgCIAEoBRISCgpkZWxheV9kYXlzGAMgASgFEhMKC2RlbGF5X2hvdXJzGAQgASgFEhUKDWRlbGF5X21pbnV0ZXMYBSABKAUSEwoLdGVtcGxhdGVfaWQYBiABKAkSHQoQc3ViamVjdF9vdmVycmlkZRgHIAEoCUgAiAEBEjAKCmNvbmRpdGlvbnMYCCABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0SAGIAQFCEwoRX3N1YmplY3Rfb3ZlcnJpZGVCDQoLX2NvbmRpdGlvbnMiTwoeQ3JlYXRlRHJpcFNlcXVlbmNlU3RlcFJlc3BvbnNlEi0KBHN0ZXAYASABKAsyHy5ibG9ja25pbmphLnYxLkRyaXBTZXF1ZW5jZVN0ZXAi8gIKHVVwZGF0ZURyaXBTZXF1ZW5jZVN0ZXBSZXF1ZXN0EgoKAmlkGAEgASgJEhcKCnN0ZXBfb3JkZXIYAiABKAVIAIgBARIXCgpkZWxheV9kYXlzGAMgASgFSAGIAQESGAoLZGVsYXlfaG91cnMYBCABKAVIAogBARIaCg1kZWxheV9taW51dGVzGAUgASgFSAOIAQESGAoLdGVtcGxhdGVfaWQYBiABKAlIBIgBARIdChBzdWJqZWN0X292ZXJyaWRlGAcgASgJSAWIAQESMAoKY29uZGl0aW9ucxgIIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3RIBogBAUINCgtfc3RlcF9vcmRlckINCgtfZGVsYXlfZGF5c0IOCgxfZGVsYXlfaG91cnNCEAoOX2RlbGF5X21pbnV0ZXNCDgoMX3RlbXBsYXRlX2lkQhMKEV9zdWJqZWN0X292ZXJyaWRlQg0KC19jb25kaXRpb25zIk8KHlVwZGF0ZURyaXBTZXF1ZW5jZVN0ZXBSZXNwb25zZRItCgRzdGVwGAEgASgLMh8uYmxvY2tuaW5qYS52MS5EcmlwU2VxdWVuY2VTdGVwIisKHURlbGV0ZURyaXBTZXF1ZW5jZVN0ZXBSZXF1ZXN0EgoKAmlkGAEgASgJIiAKHkRlbGV0ZURyaXBTZXF1ZW5jZVN0ZXBSZXNwb25zZSJICh9SZW9yZGVyRHJpcFNlcXVlbmNlU3RlcHNSZXF1ZXN0EhMKC3NlcXVlbmNlX2lkGAEgASgJEhAKCHN0ZXBfaWRzGAIgAygJIlIKIFJlb3JkZXJEcmlwU2VxdWVuY2VTdGVwc1Jlc3BvbnNlEi4KBXN0ZXBzGAEgAygLMh8uYmxvY2tuaW5qYS52MS5EcmlwU2VxdWVuY2VTdGVwItUBChhDcmVhdGVSU1NDYW1wYWlnblJlcXVlc3QSFwoPbWFpbGluZ19saXN0X2lkGAEgASgJEgwKBG5hbWUYAiABKAkSEwoLdGVtcGxhdGVfaWQYAyABKAkSEQoJZnJlcXVlbmN5GAQgASgJEhUKCHNlbmRfZGF5GAUgASgFSACIAQESEQoJc2VuZF9ob3VyGAYgASgFEhUKCHRpbWV6b25lGAcgASgJSAGIAQESDwoHZW5hYmxlZBgIIAEoCEILCglfc2VuZF9kYXlCCwoJX3RpbWV6b25lIk0KGUNyZWF0ZVJTU0NhbXBhaWduUmVzcG9uc2USMAoMcnNzX2NhbXBhaWduGAEgASgLMhouYmxvY2tuaW5qYS52MS5SU1NDYW1wYWlnbiIjChVHZXRSU1NDYW1wYWlnblJlcXVlc3QSCgoCaWQYASABKAkiSgoWR2V0UlNTQ2FtcGFpZ25SZXNwb25zZRIwCgxyc3NfY2FtcGFpZ24YASABKAsyGi5ibG9ja25pbmphLnYxLlJTU0NhbXBhaWduIjIKF0xpc3RSU1NDYW1wYWlnbnNSZXF1ZXN0EhcKD21haWxpbmdfbGlzdF9pZBgBIAEoCSJNChhMaXN0UlNTQ2FtcGFpZ25zUmVzcG9uc2USMQoNcnNzX2NhbXBhaWducxgBIAMoCzIaLmJsb2NrbmluamEudjEuUlNTQ2FtcGFpZ24iogIKGFVwZGF0ZVJTU0NhbXBhaWduUmVxdWVzdBIKCgJpZBgBIAEoCRIRCgRuYW1lGAIgASgJSACIAQESGAoLdGVtcGxhdGVfaWQYAyABKAlIAYgBARIWCglmcmVxdWVuY3kYBCABKAlIAogBARIVCghzZW5kX2RheRgFIAEoBUgDiAEBEhYKCXNlbmRfaG91chgGIAEoBUgEiAEBEhUKCHRpbWV6b25lGAcgASgJSAWIAQESFAoHZW5hYmxlZBgIIAEoCEgGiAEBQgcKBV9uYW1lQg4KDF90ZW1wbGF0ZV9pZEIMCgpfZnJlcXVlbmN5QgsKCV9zZW5kX2RheUIMCgpfc2VuZF9ob3VyQgsKCV90aW1lem9uZUIKCghfZW5hYmxlZCJNChlVcGRhdGVSU1NDYW1wYWlnblJlc3BvbnNlEjAKDHJzc19jYW1wYWlnbhgBIAEoCzIaLmJsb2NrbmluamEudjEuUlNTQ2FtcGFpZ24iJgoYRGVsZXRlUlNTQ2FtcGFpZ25SZXF1ZXN0EgoKAmlkGAEgASgJIhsKGURlbGV0ZVJTU0NhbXBhaWduUmVzcG9uc2UiIQoTR2V0TGlzdFN0YXRzUmVxdWVzdBIKCgJpZBgBIAEoCSKUAgoUR2V0TGlzdFN0YXRzUmVzcG9uc2USCgoCaWQYASABKAkSDAoEc2x1ZxgCIAEoCRIMCgRuYW1lGAMgASgJEhkKEXRvdGFsX3N1YnNjcmliZXJzGAQgASgDEhoKEmFjdGl2ZV9zdWJzY3JpYmVycxgFIAEoAxIbChNwZW5kaW5nX3N1YnNjcmliZXJzGAYgASgDEhQKDHVuc3Vic2NyaWJlZBgHIAEoAxIXCg90b3RhbF9jYW1wYWlnbnMYCCABKAMSGAoQYWN0aXZlX3NlcXVlbmNlcxgJIAEoAxIaChJuZXdfc3Vic2NyaWJlcnNfN2QYCiABKAMSGwoTbmV3X3N1YnNjcmliZXJzXzMwZBgLIAEoAyKTAQoaR2V0U3Vic2NyaWJlckdyb3d0aFJlcXVlc3QSFwoPbWFpbGluZ19saXN0X2lkGAEgASgJEi4KCnN0YXJ0X2RhdGUYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEiwKCGVuZF9kYXRlGAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCJSChVTdWJzY3JpYmVyR3Jvd3RoUG9pbnQSDAoEZGF0ZRgBIAEoCRIXCg9uZXdfc3Vic2NyaWJlcnMYAiABKAMSEgoKY3VtdWxhdGl2ZRgDIAEoAyJRChtHZXRTdWJzY3JpYmVyR3Jvd3RoUmVzcG9uc2USMgoEZGF0YRgBIAMoCzIkLmJsb2NrbmluamEudjEuU3Vic2NyaWJlckdyb3d0aFBvaW50Ir8BChhHZXRUcmFja2luZ0V2ZW50c1JlcXVlc3QSGAoLY2FtcGFpZ25faWQYASABKAlIAIgBARIdChBzdWJzY3JpYmVyX2VtYWlsGAIgASgJSAGIAQESFwoKZXZlbnRfdHlwZRgDIAEoCUgCiAEBEg0KBWxpbWl0GAQgASgFEg4KBm9mZnNldBgFIAEoBUIOCgxfY2FtcGFpZ25faWRCEwoRX3N1YnNjcmliZXJfZW1haWxCDQoLX2V2ZW50X3R5cGUiWAoZR2V0VHJhY2tpbmdFdmVudHNSZXNwb25zZRIsCgZldmVudHMYASADKAsyHC5ibG9ja25pbmphLnYxLlRyYWNraW5nRXZlbnQSDQoFdG90YWwYAiABKAMy+iUKE01haWxpbmdMaXN0c1NlcnZpY2USZgoRQ3JlYXRlTWFpbGluZ0xpc3QSJy5ibG9ja25pbmphLnYxLkNyZWF0ZU1haWxpbmdMaXN0UmVxdWVzdBooLmJsb2NrbmluamEudjEuQ3JlYXRlTWFpbGluZ0xpc3RSZXNwb25zZRJdCg5HZXRNYWlsaW5nTGlzdBIkLmJsb2NrbmluamEudjEuR2V0TWFpbGluZ0xpc3RSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5HZXRNYWlsaW5nTGlzdFJlc3BvbnNlEm8KFEdldE1haWxpbmdMaXN0QnlTbHVnEiouYmxvY2tuaW5qYS52MS5HZXRNYWlsaW5nTGlzdEJ5U2x1Z1JlcXVlc3QaKy5ibG9ja25pbmphLnYxLkdldE1haWxpbmdMaXN0QnlTbHVnUmVzcG9uc2USYwoQTGlzdE1haWxpbmdMaXN0cxImLmJsb2NrbmluamEudjEuTGlzdE1haWxpbmdMaXN0c1JlcXVlc3QaJy5ibG9ja25pbmphLnYxLkxpc3RNYWlsaW5nTGlzdHNSZXNwb25zZRJmChFVcGRhdGVNYWlsaW5nTGlzdBInLmJsb2NrbmluamEudjEuVXBkYXRlTWFpbGluZ0xpc3RSZXF1ZXN0GiguYmxvY2tuaW5qYS52MS5VcGRhdGVNYWlsaW5nTGlzdFJlc3BvbnNlEmYKEURlbGV0ZU1haWxpbmdMaXN0EicuYmxvY2tuaW5qYS52MS5EZWxldGVNYWlsaW5nTGlzdFJlcXVlc3QaKC5ibG9ja25pbmphLnYxLkRlbGV0ZU1haWxpbmdMaXN0UmVzcG9uc2USbwoUR2V0TWFpbGluZ0xpc3RTY2hlbWESKi5ibG9ja25pbmphLnYxLkdldE1haWxpbmdMaXN0U2NoZW1hUmVxdWVzdBorLmJsb2NrbmluamEudjEuR2V0TWFpbGluZ0xpc3RTY2hlbWFSZXNwb25zZRJ4ChdVcGRhdGVNYWlsaW5nTGlzdFNjaGVtYRItLmJsb2NrbmluamEudjEuVXBkYXRlTWFpbGluZ0xpc3RTY2hlbWFSZXF1ZXN0Gi4uYmxvY2tuaW5qYS52MS5VcGRhdGVNYWlsaW5nTGlzdFNjaGVtYVJlc3BvbnNlEl0KDkFkZEN1c3RvbUZpZWxkEiQuYmxvY2tuaW5qYS52MS5BZGRDdXN0b21GaWVsZFJlcXVlc3QaJS5ibG9ja25pbmphLnYxLkFkZEN1c3RvbUZpZWxkUmVzcG9uc2USZgoRUmVtb3ZlQ3VzdG9tRmllbGQSJy5ibG9ja25pbmphLnYxLlJlbW92ZUN1c3RvbUZpZWxkUmVxdWVzdBooLmJsb2NrbmluamEudjEuUmVtb3ZlQ3VzdG9tRmllbGRSZXNwb25zZRJgCg9MaXN0U3Vic2NyaWJlcnMSJS5ibG9ja25pbmphLnYxLkxpc3RTdWJzY3JpYmVyc1JlcXVlc3QaJi5ibG9ja25pbmphLnYxLkxpc3RTdWJzY3JpYmVyc1Jlc3BvbnNlEloKDUdldFN1YnNjcmliZXISIy5ibG9ja25pbmphLnYxLkdldFN1YnNjcmliZXJSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5HZXRTdWJzY3JpYmVyUmVzcG9uc2USYwoQQ3JlYXRlU3Vic2NyaWJlchImLmJsb2NrbmluamEudjEuQ3JlYXRlU3Vic2NyaWJlclJlcXVlc3QaJy5ibG9ja25pbmphLnYxLkNyZWF0ZVN1YnNjcmliZXJSZXNwb25zZRJjChBVcGRhdGVTdWJzY3JpYmVyEiYuYmxvY2tuaW5qYS52MS5VcGRhdGVTdWJzY3JpYmVyUmVxdWVzdBonLmJsb2NrbmluamEudjEuVXBkYXRlU3Vic2NyaWJlclJlc3BvbnNlEmMKEERlbGV0ZVN1YnNjcmliZXISJi5ibG9ja25pbmphLnYxLkRlbGV0ZVN1YnNjcmliZXJSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5EZWxldGVTdWJzY3JpYmVyUmVzcG9uc2USZgoRSW1wb3J0U3Vic2NyaWJlcnMSJy5ibG9ja25pbmphLnYxLkltcG9ydFN1YnNjcmliZXJzUmVxdWVzdBooLmJsb2NrbmluamEudjEuSW1wb3J0U3Vic2NyaWJlcnNSZXNwb25zZRJmChFFeHBvcnRTdWJzY3JpYmVycxInLmJsb2NrbmluamEudjEuRXhwb3J0U3Vic2NyaWJlcnNSZXF1ZXN0GiguYmxvY2tuaW5qYS52MS5FeHBvcnRTdWJzY3JpYmVyc1Jlc3BvbnNlEmAKD1B1YmxpY1N1YnNjcmliZRIlLmJsb2NrbmluamEudjEuUHVibGljU3Vic2NyaWJlUmVxdWVzdBomLmJsb2NrbmluamEudjEuUHVibGljU3Vic2NyaWJlUmVzcG9uc2USWgoNUHVibGljQ29uZmlybRIjLmJsb2NrbmluamEudjEuUHVibGljQ29uZmlybVJlcXVlc3QaJC5ibG9ja25pbmphLnYxLlB1YmxpY0NvbmZpcm1SZXNwb25zZRJmChFQdWJsaWNVbnN1YnNjcmliZRInLmJsb2NrbmluamEudjEuUHVibGljVW5zdWJzY3JpYmVSZXF1ZXN0GiguYmxvY2tuaW5qYS52MS5QdWJsaWNVbnN1YnNjcmliZVJlc3BvbnNlEngKF1B1YmxpY1VwZGF0ZVByZWZlcmVuY2VzEi0uYmxvY2tuaW5qYS52MS5QdWJsaWNVcGRhdGVQcmVmZXJlbmNlc1JlcXVlc3QaLi5ibG9ja25pbmphLnYxLlB1YmxpY1VwZGF0ZVByZWZlcmVuY2VzUmVzcG9uc2USXQoOQ3JlYXRlQ2FtcGFpZ24SJC5ibG9ja25pbmphLnYxLkNyZWF0ZUNhbXBhaWduUmVxdWVzdBolLmJsb2NrbmluamEudjEuQ3JlYXRlQ2FtcGFpZ25SZXNwb25zZRJUCgtHZXRDYW1wYWlnbhIhLmJsb2NrbmluamEudjEuR2V0Q2FtcGFpZ25SZXF1ZXN0GiIuYmxvY2tuaW5qYS52MS5HZXRDYW1wYWlnblJlc3BvbnNlEloKDUxpc3RDYW1wYWlnbnMSIy5ibG9ja25pbmphLnYxLkxpc3RDYW1wYWlnbnNSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5MaXN0Q2FtcGFpZ25zUmVzcG9uc2USXQoOVXBkYXRlQ2FtcGFpZ24SJC5ibG9ja25pbmphLnYxLlVwZGF0ZUNhbXBhaWduUmVxdWVzdBolLmJsb2NrbmluamEudjEuVXBkYXRlQ2FtcGFpZ25SZXNwb25zZRJdCg5EZWxldGVDYW1wYWlnbhIkLmJsb2NrbmluamEudjEuRGVsZXRlQ2FtcGFpZ25SZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5EZWxldGVDYW1wYWlnblJlc3BvbnNlEmMKEFNjaGVkdWxlQ2FtcGFpZ24SJi5ibG9ja25pbmphLnYxLlNjaGVkdWxlQ2FtcGFpZ25SZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5TY2hlZHVsZUNhbXBhaWduUmVzcG9uc2USVwoMU2VuZENhbXBhaWduEiIuYmxvY2tuaW5qYS52MS5TZW5kQ2FtcGFpZ25SZXF1ZXN0GiMuYmxvY2tuaW5qYS52MS5TZW5kQ2FtcGFpZ25SZXNwb25zZRJdCg5DYW5jZWxDYW1wYWlnbhIkLmJsb2NrbmluamEudjEuQ2FuY2VsQ2FtcGFpZ25SZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5DYW5jZWxDYW1wYWlnblJlc3BvbnNlEmMKEEdldENhbXBhaWduU3RhdHMSJi5ibG9ja25pbmphLnYxLkdldENhbXBhaWduU3RhdHNSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5HZXRDYW1wYWlnblN0YXRzUmVzcG9uc2USaQoSQ3JlYXRlRHJpcFNlcXVlbmNlEiguYmxvY2tuaW5qYS52MS5DcmVhdGVEcmlwU2VxdWVuY2VSZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5DcmVhdGVEcmlwU2VxdWVuY2VSZXNwb25zZRJgCg9HZXREcmlwU2VxdWVuY2USJS5ibG9ja25pbmphLnYxLkdldERyaXBTZXF1ZW5jZVJlcXVlc3QaJi5ibG9ja25pbmphLnYxLkdldERyaXBTZXF1ZW5jZVJlc3BvbnNlEmYKEUxpc3REcmlwU2VxdWVuY2VzEicuYmxvY2tuaW5qYS52MS5MaXN0RHJpcFNlcXVlbmNlc1JlcXVlc3QaKC5ibG9ja25pbmphLnYxLkxpc3REcmlwU2VxdWVuY2VzUmVzcG9uc2USaQoSVXBkYXRlRHJpcFNlcXVlbmNlEiguYmxvY2tuaW5qYS52MS5VcGRhdGVEcmlwU2VxdWVuY2VSZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5VcGRhdGVEcmlwU2VxdWVuY2VSZXNwb25zZRJpChJEZWxldGVEcmlwU2VxdWVuY2USKC5ibG9ja25pbmphLnYxLkRlbGV0ZURyaXBTZXF1ZW5jZVJlcXVlc3QaKS5ibG9ja25pbmphLnYxLkRlbGV0ZURyaXBTZXF1ZW5jZVJlc3BvbnNlEnUKFkNyZWF0ZURyaXBTZXF1ZW5jZVN0ZXASLC5ibG9ja25pbmphLnYxLkNyZWF0ZURyaXBTZXF1ZW5jZVN0ZXBSZXF1ZXN0Gi0uYmxvY2tuaW5qYS52MS5DcmVhdGVEcmlwU2VxdWVuY2VTdGVwUmVzcG9uc2USdQoWVXBkYXRlRHJpcFNlcXVlbmNlU3RlcBIsLmJsb2NrbmluamEudjEuVXBkYXRlRHJpcFNlcXVlbmNlU3RlcFJlcXVlc3QaLS5ibG9ja25pbmphLnYxLlVwZGF0ZURyaXBTZXF1ZW5jZVN0ZXBSZXNwb25zZRJ1ChZEZWxldGVEcmlwU2VxdWVuY2VTdGVwEiwuYmxvY2tuaW5qYS52MS5EZWxldGVEcmlwU2VxdWVuY2VTdGVwUmVxdWVzdBotLmJsb2NrbmluamEudjEuRGVsZXRlRHJpcFNlcXVlbmNlU3RlcFJlc3BvbnNlEnsKGFJlb3JkZXJEcmlwU2VxdWVuY2VTdGVwcxIuLmJsb2NrbmluamEudjEuUmVvcmRlckRyaXBTZXF1ZW5jZVN0ZXBzUmVxdWVzdBovLmJsb2NrbmluamEudjEuUmVvcmRlckRyaXBTZXF1ZW5jZVN0ZXBzUmVzcG9uc2USZgoRQ3JlYXRlUlNTQ2FtcGFpZ24SJy5ibG9ja25pbmphLnYxLkNyZWF0ZVJTU0NhbXBhaWduUmVxdWVzdBooLmJsb2NrbmluamEudjEuQ3JlYXRlUlNTQ2FtcGFpZ25SZXNwb25zZRJdCg5HZXRSU1NDYW1wYWlnbhIkLmJsb2NrbmluamEudjEuR2V0UlNTQ2FtcGFpZ25SZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5HZXRSU1NDYW1wYWlnblJlc3BvbnNlEmMKEExpc3RSU1NDYW1wYWlnbnMSJi5ibG9ja25pbmphLnYxLkxpc3RSU1NDYW1wYWlnbnNSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5MaXN0UlNTQ2FtcGFpZ25zUmVzcG9uc2USZgoRVXBkYXRlUlNTQ2FtcGFpZ24SJy5ibG9ja25pbmphLnYxLlVwZGF0ZVJTU0NhbXBhaWduUmVxdWVzdBooLmJsb2NrbmluamEudjEuVXBkYXRlUlNTQ2FtcGFpZ25SZXNwb25zZRJmChFEZWxldGVSU1NDYW1wYWlnbhInLmJsb2NrbmluamEudjEuRGVsZXRlUlNTQ2FtcGFpZ25SZXF1ZXN0GiguYmxvY2tuaW5qYS52MS5EZWxldGVSU1NDYW1wYWlnblJlc3BvbnNlElcKDEdldExpc3RTdGF0cxIiLmJsb2NrbmluamEudjEuR2V0TGlzdFN0YXRzUmVxdWVzdBojLmJsb2NrbmluamEudjEuR2V0TGlzdFN0YXRzUmVzcG9uc2USbAoTR2V0U3Vic2NyaWJlckdyb3d0aBIpLmJsb2NrbmluamEudjEuR2V0U3Vic2NyaWJlckdyb3d0aFJlcXVlc3QaKi5ibG9ja25pbmphLnYxLkdldFN1YnNjcmliZXJHcm93dGhSZXNwb25zZRJmChFHZXRUcmFja2luZ0V2ZW50cxInLmJsb2NrbmluamEudjEuR2V0VHJhY2tpbmdFdmVudHNSZXF1ZXN0GiguYmxvY2tuaW5qYS52MS5HZXRUcmFja2luZ0V2ZW50c1Jlc3BvbnNlQscBChFjb20uYmxvY2tuaW5qYS52MUIRTWFpbGluZ0xpc3RzUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_google_protobuf_struct, file_google_protobuf_timestamp, file_blockninja_v1_data_tables], +); +/** + * @generated from service blockninja.v1.MailingListsService + */ +const MailingListsService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_mailing_lists, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/mailing_lists.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * List CRUD + * + * @generated from rpc blockninja.v1.MailingListsService.CreateMailingList + */ +MailingListsService.method.createMailingList; +/** + * @generated from rpc blockninja.v1.MailingListsService.GetMailingList + */ +MailingListsService.method.getMailingList; +/** + * @generated from rpc blockninja.v1.MailingListsService.GetMailingListBySlug + */ +MailingListsService.method.getMailingListBySlug; +/** + * @generated from rpc blockninja.v1.MailingListsService.ListMailingLists + */ +MailingListsService.method.listMailingLists; +/** + * @generated from rpc blockninja.v1.MailingListsService.UpdateMailingList + */ +MailingListsService.method.updateMailingList; +/** + * @generated from rpc blockninja.v1.MailingListsService.DeleteMailingList + */ +MailingListsService.method.deleteMailingList; +/** + * Schema (custom fields) + * + * @generated from rpc blockninja.v1.MailingListsService.GetMailingListSchema + */ +MailingListsService.method.getMailingListSchema; +/** + * @generated from rpc blockninja.v1.MailingListsService.UpdateMailingListSchema + */ +MailingListsService.method.updateMailingListSchema; +/** + * @generated from rpc blockninja.v1.MailingListsService.AddCustomField + */ +MailingListsService.method.addCustomField; +/** + * @generated from rpc blockninja.v1.MailingListsService.RemoveCustomField + */ +MailingListsService.method.removeCustomField; +/** + * Subscribers (convenience wrappers around data table rows) + * + * @generated from rpc blockninja.v1.MailingListsService.ListSubscribers + */ +MailingListsService.method.listSubscribers; +/** + * @generated from rpc blockninja.v1.MailingListsService.GetSubscriber + */ +MailingListsService.method.getSubscriber; +/** + * @generated from rpc blockninja.v1.MailingListsService.CreateSubscriber + */ +MailingListsService.method.createSubscriber; +/** + * @generated from rpc blockninja.v1.MailingListsService.UpdateSubscriber + */ +MailingListsService.method.updateSubscriber; +/** + * @generated from rpc blockninja.v1.MailingListsService.DeleteSubscriber + */ +MailingListsService.method.deleteSubscriber; +/** + * @generated from rpc blockninja.v1.MailingListsService.ImportSubscribers + */ +MailingListsService.method.importSubscribers; +/** + * @generated from rpc blockninja.v1.MailingListsService.ExportSubscribers + */ +MailingListsService.method.exportSubscribers; +/** + * Public endpoints (no auth required) + * + * @generated from rpc blockninja.v1.MailingListsService.PublicSubscribe + */ +MailingListsService.method.publicSubscribe; +/** + * @generated from rpc blockninja.v1.MailingListsService.PublicConfirm + */ +MailingListsService.method.publicConfirm; +/** + * @generated from rpc blockninja.v1.MailingListsService.PublicUnsubscribe + */ +MailingListsService.method.publicUnsubscribe; +/** + * @generated from rpc blockninja.v1.MailingListsService.PublicUpdatePreferences + */ +MailingListsService.method.publicUpdatePreferences; +/** + * Campaigns + * + * @generated from rpc blockninja.v1.MailingListsService.CreateCampaign + */ +MailingListsService.method.createCampaign; +/** + * @generated from rpc blockninja.v1.MailingListsService.GetCampaign + */ +MailingListsService.method.getCampaign; +/** + * @generated from rpc blockninja.v1.MailingListsService.ListCampaigns + */ +MailingListsService.method.listCampaigns; +/** + * @generated from rpc blockninja.v1.MailingListsService.UpdateCampaign + */ +MailingListsService.method.updateCampaign; +/** + * @generated from rpc blockninja.v1.MailingListsService.DeleteCampaign + */ +MailingListsService.method.deleteCampaign; +/** + * @generated from rpc blockninja.v1.MailingListsService.ScheduleCampaign + */ +MailingListsService.method.scheduleCampaign; +/** + * @generated from rpc blockninja.v1.MailingListsService.SendCampaign + */ +MailingListsService.method.sendCampaign; +/** + * @generated from rpc blockninja.v1.MailingListsService.CancelCampaign + */ +MailingListsService.method.cancelCampaign; +/** + * @generated from rpc blockninja.v1.MailingListsService.GetCampaignStats + */ +MailingListsService.method.getCampaignStats; +/** + * Drip Sequences + * + * @generated from rpc blockninja.v1.MailingListsService.CreateDripSequence + */ +MailingListsService.method.createDripSequence; +/** + * @generated from rpc blockninja.v1.MailingListsService.GetDripSequence + */ +MailingListsService.method.getDripSequence; +/** + * @generated from rpc blockninja.v1.MailingListsService.ListDripSequences + */ +MailingListsService.method.listDripSequences; +/** + * @generated from rpc blockninja.v1.MailingListsService.UpdateDripSequence + */ +MailingListsService.method.updateDripSequence; +/** + * @generated from rpc blockninja.v1.MailingListsService.DeleteDripSequence + */ +MailingListsService.method.deleteDripSequence; +/** + * Drip Sequence Steps + * + * @generated from rpc blockninja.v1.MailingListsService.CreateDripSequenceStep + */ +MailingListsService.method.createDripSequenceStep; +/** + * @generated from rpc blockninja.v1.MailingListsService.UpdateDripSequenceStep + */ +MailingListsService.method.updateDripSequenceStep; +/** + * @generated from rpc blockninja.v1.MailingListsService.DeleteDripSequenceStep + */ +MailingListsService.method.deleteDripSequenceStep; +/** + * @generated from rpc blockninja.v1.MailingListsService.ReorderDripSequenceSteps + */ +MailingListsService.method.reorderDripSequenceSteps; +/** + * RSS Campaigns + * + * @generated from rpc blockninja.v1.MailingListsService.CreateRSSCampaign + */ +MailingListsService.method.createRSSCampaign; +/** + * @generated from rpc blockninja.v1.MailingListsService.GetRSSCampaign + */ +MailingListsService.method.getRSSCampaign; +/** + * @generated from rpc blockninja.v1.MailingListsService.ListRSSCampaigns + */ +MailingListsService.method.listRSSCampaigns; +/** + * @generated from rpc blockninja.v1.MailingListsService.UpdateRSSCampaign + */ +MailingListsService.method.updateRSSCampaign; +/** + * @generated from rpc blockninja.v1.MailingListsService.DeleteRSSCampaign + */ +MailingListsService.method.deleteRSSCampaign; +/** + * Stats & Analytics + * + * @generated from rpc blockninja.v1.MailingListsService.GetListStats + */ +MailingListsService.method.getListStats; +/** + * @generated from rpc blockninja.v1.MailingListsService.GetSubscriberGrowth + */ +MailingListsService.method.getSubscriberGrowth; +/** + * @generated from rpc blockninja.v1.MailingListsService.GetTrackingEvents + */ +MailingListsService.method.getTrackingEvents; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/management.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/management.proto. + */ +const file_blockninja_v1_management = /*@__PURE__*/ fileDesc( + "Ch5ibG9ja25pbmphL3YxL21hbmFnZW1lbnQucHJvdG8SDWJsb2NrbmluamEudjEiSAoXTGlzdE1hbmFnZWRVc2Vyc1JlcXVlc3QSLQoKcGFnaW5hdGlvbhgBIAEoCzIZLmJsb2NrbmluamEudjEuUGFnaW5hdGlvbiJ8ChhMaXN0TWFuYWdlZFVzZXJzUmVzcG9uc2USKQoFdXNlcnMYASADKAsyGi5ibG9ja25pbmphLnYxLk1hbmFnZWRVc2VyEjUKCnBhZ2luYXRpb24YAiABKAsyIS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb25SZXNwb25zZSJNChVHZXRNYW5hZ2VkVXNlclJlcXVlc3QSEgoFZW1haWwYASABKAlIAIgBARIPCgJpZBgCIAEoCUgBiAEBQggKBl9lbWFpbEIFCgNfaWQiQgoWR2V0TWFuYWdlZFVzZXJSZXNwb25zZRIoCgR1c2VyGAEgASgLMhouYmxvY2tuaW5qYS52MS5NYW5hZ2VkVXNlciKZAQoPU3luY1VzZXJSZXF1ZXN0Eg0KBWVtYWlsGAEgASgJEhIKCmZpcnN0X25hbWUYAiABKAkSEQoJbGFzdF9uYW1lGAMgASgJEiEKBHJvbGUYBCABKA4yEy5ibG9ja25pbmphLnYxLlJvbGUSHAoUb3JjaGVzdHJhdG9yX3VzZXJfaWQYBSABKAkSDwoHaGFzX3NzbxgGIAEoCCJNChBTeW5jVXNlclJlc3BvbnNlEigKBHVzZXIYASABKAsyGi5ibG9ja25pbmphLnYxLk1hbmFnZWRVc2VyEg8KB2NyZWF0ZWQYAiABKAgiXAoRUmVtb3ZlVXNlclJlcXVlc3QSEgoFZW1haWwYASABKAlIAIgBARIPCgJpZBgCIAEoCUgBiAEBEhEKCXBlcm1hbmVudBgDIAEoCEIICgZfZW1haWxCBQoDX2lkIhQKElJlbW92ZVVzZXJSZXNwb25zZSIWChRHZXRBdXRoQ29uZmlnUmVxdWVzdCKAAQoVR2V0QXV0aENvbmZpZ1Jlc3BvbnNlEhkKEW9yY2hlc3RyYXRvcl9tb2RlGAEgASgIEhgKEG9yY2hlc3RyYXRvcl91cmwYAiABKAkSHQoVbG9jYWxfbG9naW5fYXZhaWxhYmxlGAMgASgIEhMKC2luc3RhbmNlX2lkGAQgASgJItoDCgtNYW5hZ2VkVXNlchIKCgJpZBgBIAEoCRINCgVlbWFpbBgCIAEoCRIhCgRyb2xlGAMgASgOMhMuYmxvY2tuaW5qYS52MS5Sb2xlEhIKCmZpcnN0X25hbWUYBCABKAkSEQoJbGFzdF9uYW1lGAUgASgJEhEKCWlzX2FjdGl2ZRgGIAEoCBI2Cg1sYXN0X2xvZ2luX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgAiAEBEi4KCmNyZWF0ZWRfYXQYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEiEKFG9yY2hlc3RyYXRvcl91c2VyX2lkGAogASgJSAGIAQESDwoHaGFzX3NzbxgLIAEoCBIaChJoYXNfbG9jYWxfcGFzc3dvcmQYDSABKAgSMgoJc3luY2VkX2F0GAwgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgCiAEBQhAKDl9sYXN0X2xvZ2luX2F0QhcKFV9vcmNoZXN0cmF0b3JfdXNlcl9pZEIMCgpfc3luY2VkX2F0InIKGE5vdGlmeVBsYW5DaGFuZ2VkUmVxdWVzdBIOCgZyZWFzb24YASABKAkSFAoHcGxhbl9pZBgCIAEoCUgAiAEBEhYKCXBsYW5fbmFtZRgDIAEoCUgBiAEBQgoKCF9wbGFuX2lkQgwKCl9wbGFuX25hbWUiTAoZTm90aWZ5UGxhbkNoYW5nZWRSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEgwKBHRpZXIYAiABKAkSEAoIZmVhdHVyZXMYAyADKAkiPgoWU2V0VXNlclBhc3N3b3JkUmVxdWVzdBINCgVlbWFpbBgBIAEoCRIVCg1wYXNzd29yZF9oYXNoGAIgASgJIioKF1NldFVzZXJQYXNzd29yZFJlc3BvbnNlEg8KB3N1Y2Nlc3MYASABKAgynQUKEU1hbmFnZW1lbnRTZXJ2aWNlEmMKEExpc3RNYW5hZ2VkVXNlcnMSJi5ibG9ja25pbmphLnYxLkxpc3RNYW5hZ2VkVXNlcnNSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5MaXN0TWFuYWdlZFVzZXJzUmVzcG9uc2USXQoOR2V0TWFuYWdlZFVzZXISJC5ibG9ja25pbmphLnYxLkdldE1hbmFnZWRVc2VyUmVxdWVzdBolLmJsb2NrbmluamEudjEuR2V0TWFuYWdlZFVzZXJSZXNwb25zZRJLCghTeW5jVXNlchIeLmJsb2NrbmluamEudjEuU3luY1VzZXJSZXF1ZXN0Gh8uYmxvY2tuaW5qYS52MS5TeW5jVXNlclJlc3BvbnNlElEKClJlbW92ZVVzZXISIC5ibG9ja25pbmphLnYxLlJlbW92ZVVzZXJSZXF1ZXN0GiEuYmxvY2tuaW5qYS52MS5SZW1vdmVVc2VyUmVzcG9uc2USWgoNR2V0QXV0aENvbmZpZxIjLmJsb2NrbmluamEudjEuR2V0QXV0aENvbmZpZ1JlcXVlc3QaJC5ibG9ja25pbmphLnYxLkdldEF1dGhDb25maWdSZXNwb25zZRJmChFOb3RpZnlQbGFuQ2hhbmdlZBInLmJsb2NrbmluamEudjEuTm90aWZ5UGxhbkNoYW5nZWRSZXF1ZXN0GiguYmxvY2tuaW5qYS52MS5Ob3RpZnlQbGFuQ2hhbmdlZFJlc3BvbnNlEmAKD1NldFVzZXJQYXNzd29yZBIlLmJsb2NrbmluamEudjEuU2V0VXNlclBhc3N3b3JkUmVxdWVzdBomLmJsb2NrbmluamEudjEuU2V0VXNlclBhc3N3b3JkUmVzcG9uc2VCxQEKEWNvbS5ibG9ja25pbmphLnYxQg9NYW5hZ2VtZW50UHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_blockninja_v1_auth, file_blockninja_v1_common, file_google_protobuf_timestamp], +); +/** + * ManagementService provides orchestrator-to-CMS user management. + * All methods require X-Orchestrator-Secret header authentication. + * + * @generated from service blockninja.v1.ManagementService + */ +const ManagementService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_management, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/management.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * List all admin users in this CMS instance + * + * @generated from rpc blockninja.v1.ManagementService.ListManagedUsers + */ +ManagementService.method.listManagedUsers; +/** + * Get a specific user by email or ID + * + * @generated from rpc blockninja.v1.ManagementService.GetManagedUser + */ +ManagementService.method.getManagedUser; +/** + * Sync (upsert) a user from orchestrator - creates or updates by email + * + * @generated from rpc blockninja.v1.ManagementService.SyncUser + */ +ManagementService.method.syncUser; +/** + * Remove/deactivate a user + * + * @generated from rpc blockninja.v1.ManagementService.RemoveUser + */ +ManagementService.method.removeUser; +/** + * Get authentication configuration for this instance + * + * @generated from rpc blockninja.v1.ManagementService.GetAuthConfig + */ +ManagementService.method.getAuthConfig; +/** + * Notify CMS that plan/subscription has changed - triggers capabilities refresh + * + * @generated from rpc blockninja.v1.ManagementService.NotifyPlanChanged + */ +ManagementService.method.notifyPlanChanged; +/** + * Set a user's password (called by orchestrator when password is changed there) + * + * @generated from rpc blockninja.v1.ManagementService.SetUserPassword + */ +ManagementService.method.setUserPassword; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/media.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/media.proto. + */ +const file_blockninja_v1_media = /*@__PURE__*/ fileDesc( + "ChlibG9ja25pbmphL3YxL21lZGlhLnByb3RvEg1ibG9ja25pbmphLnYxIuEECgVNZWRpYRIKCgJpZBgBIAEoCRIQCghmaWxlbmFtZRgCIAEoCRIVCg1vcmlnaW5hbF9wYXRoGAMgASgJEhEKCXdlYnBfcGF0aBgEIAEoCRItCgp0aHVtYm5haWxzGAUgASgLMhkuYmxvY2tuaW5qYS52MS5UaHVtYm5haWxzEhEKCWZpbGVfc2l6ZRgGIAEoAxIWCg5vcHRpbWl6ZWRfc2l6ZRgHIAEoAxIRCgltaW1lX3R5cGUYCCABKAkSEgoKbWVkaWFfdHlwZRgJIAEoCRINCgV3aWR0aBgKIAEoBRIOCgZoZWlnaHQYCyABKAUSEAoIZHVyYXRpb24YDCABKAUSEAoIYWx0X3RleHQYDSABKAkSDAoEdGFncxgOIAMoCRITCgt1c2FnZV9jb3VudBgPIAEoBRIuCgpjcmVhdGVkX2F0GBAgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBISCgpjcmVhdGVkX2J5GBEgASgJEhYKCWZvbGRlcl9pZBgSIAEoCUgAiAEBEjEKCHRha2VuX2F0GBMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgBiAEBEhkKDGNhbWVyYV9tb2RlbBgUIAEoCUgCiAEBEjYKDG9wdGltaXphdGlvbhgVIAEoCzIgLmJsb2NrbmluamEudjEuT3B0aW1pemF0aW9uU3RhdHMSGwoTdmlkZW9fdGh1bWJuYWlsX3VybBgWIAEoCUIMCgpfZm9sZGVyX2lkQgsKCV90YWtlbl9hdEIPCg1fY2FtZXJhX21vZGVsIocBChFPcHRpbWl6YXRpb25TdGF0cxIWCg5vcmlnaW5hbF9ieXRlcxgBIAEoAxIXCg9vcHRpbWl6ZWRfYnl0ZXMYAiABKAMSFwoPc2F2aW5nc19wZXJjZW50GAMgASgCEhAKCGhhc193ZWJwGAQgASgIEhYKDmhhc190aHVtYm5haWxzGAUgASgIIjAKClRodW1ibmFpbHMSCgoCc20YASABKAkSCgoCbWQYAiABKAkSCgoCbGcYAyABKAkibAoLTWVkaWFGb2xkZXISCgoCaWQYASABKAkSDAoEbmFtZRgCIAEoCRITCgttZWRpYV9jb3VudBgDIAEoBRIuCgpjcmVhdGVkX2F0GAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCLPAwoQTGlzdE1lZGlhUmVxdWVzdBItCgpwYWdpbmF0aW9uGAEgASgLMhkuYmxvY2tuaW5qYS52MS5QYWdpbmF0aW9uEhMKBnNlYXJjaBgCIAEoCUgAiAEBEhIKCnRhZ19maWx0ZXIYAyADKAkSHQoQbWltZV90eXBlX2ZpbHRlchgEIAEoCUgBiAEBEh4KEW1lZGlhX3R5cGVfZmlsdGVyGAUgASgJSAKIAQESFgoJZm9sZGVyX2lkGAYgASgJSAOIAQESFAoHc29ydF9ieRgHIAEoCUgEiAEBEhcKCnNvcnRfb3JkZXIYCCABKAlIBYgBARIyCglkYXRlX2Zyb20YCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSAaIAQESMAoHZGF0ZV90bxgKIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIB4gBAUIJCgdfc2VhcmNoQhMKEV9taW1lX3R5cGVfZmlsdGVyQhQKEl9tZWRpYV90eXBlX2ZpbHRlckIMCgpfZm9sZGVyX2lkQgoKCF9zb3J0X2J5Qg0KC19zb3J0X29yZGVyQgwKCl9kYXRlX2Zyb21CCgoIX2RhdGVfdG8ibwoRTGlzdE1lZGlhUmVzcG9uc2USIwoFbWVkaWEYASADKAsyFC5ibG9ja25pbmphLnYxLk1lZGlhEjUKCnBhZ2luYXRpb24YAiABKAsyIS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb25SZXNwb25zZSIdCg9HZXRNZWRpYVJlcXVlc3QSCgoCaWQYASABKAkiNwoQR2V0TWVkaWFSZXNwb25zZRIjCgVtZWRpYRgBIAEoCzIULmJsb2NrbmluamEudjEuTWVkaWEijgEKElVwZGF0ZU1lZGlhUmVxdWVzdBIKCgJpZBgBIAEoCRIVCghmaWxlbmFtZRgCIAEoCUgAiAEBEhUKCGFsdF90ZXh0GAMgASgJSAGIAQESFgoJZm9sZGVyX2lkGAQgASgJSAKIAQFCCwoJX2ZpbGVuYW1lQgsKCV9hbHRfdGV4dEIMCgpfZm9sZGVyX2lkIjoKE1VwZGF0ZU1lZGlhUmVzcG9uc2USIwoFbWVkaWEYASABKAsyFC5ibG9ja25pbmphLnYxLk1lZGlhIiAKEkRlbGV0ZU1lZGlhUmVxdWVzdBIKCgJpZBgBIAEoCSIVChNEZWxldGVNZWRpYVJlc3BvbnNlIn4KCk1lZGlhVXNhZ2USDwoHcGFnZV9pZBgBIAEoCRISCgpwYWdlX3RpdGxlGAIgASgJEhEKCXBhZ2Vfc2x1ZxgDIAEoCRIQCghibG9ja19pZBgEIAEoCRITCgtibG9ja190aXRsZRgFIAEoCRIRCglibG9ja19rZXkYBiABKAkiKAoUR2V0TWVkaWFVc2FnZVJlcXVlc3QSEAoIbWVkaWFfaWQYASABKAkiQgoVR2V0TWVkaWFVc2FnZVJlc3BvbnNlEikKBnVzYWdlcxgBIAMoCzIZLmJsb2NrbmluamEudjEuTWVkaWFVc2FnZSIcChpHZW5lcmF0ZVVwbG9hZFRva2VuUmVxdWVzdCJIChtHZW5lcmF0ZVVwbG9hZFRva2VuUmVzcG9uc2USDQoFdG9rZW4YASABKAkSGgoSZXhwaXJlc19pbl9zZWNvbmRzGAIgASgDIhQKEkxpc3RGb2xkZXJzUmVxdWVzdCJCChNMaXN0Rm9sZGVyc1Jlc3BvbnNlEisKB2ZvbGRlcnMYASADKAsyGi5ibG9ja25pbmphLnYxLk1lZGlhRm9sZGVyIiMKE0NyZWF0ZUZvbGRlclJlcXVlc3QSDAoEbmFtZRgBIAEoCSJCChRDcmVhdGVGb2xkZXJSZXNwb25zZRIqCgZmb2xkZXIYASABKAsyGi5ibG9ja25pbmphLnYxLk1lZGlhRm9sZGVyIi8KE1VwZGF0ZUZvbGRlclJlcXVlc3QSCgoCaWQYASABKAkSDAoEbmFtZRgCIAEoCSJCChRVcGRhdGVGb2xkZXJSZXNwb25zZRIqCgZmb2xkZXIYASABKAsyGi5ibG9ja25pbmphLnYxLk1lZGlhRm9sZGVyIiEKE0RlbGV0ZUZvbGRlclJlcXVlc3QSCgoCaWQYASABKAkiFgoURGVsZXRlRm9sZGVyUmVzcG9uc2UiJQoWQnVsa0RlbGV0ZU1lZGlhUmVxdWVzdBILCgNpZHMYASADKAkiMAoXQnVsa0RlbGV0ZU1lZGlhUmVzcG9uc2USFQoNZGVsZXRlZF9jb3VudBgBIAEoBSJJChRCdWxrTW92ZU1lZGlhUmVxdWVzdBILCgNpZHMYASADKAkSFgoJZm9sZGVyX2lkGAIgASgJSACIAQFCDAoKX2ZvbGRlcl9pZCIsChVCdWxrTW92ZU1lZGlhUmVzcG9uc2USEwoLbW92ZWRfY291bnQYASABKAUiRAoTQnVsa1RhZ01lZGlhUmVxdWVzdBILCgNpZHMYASADKAkSDwoHdGFnX2lkcxgCIAMoCRIPCgdyZXBsYWNlGAMgASgIIi0KFEJ1bGtUYWdNZWRpYVJlc3BvbnNlEhUKDXVwZGF0ZWRfY291bnQYASABKAUymQkKDE1lZGlhU2VydmljZRJOCglMaXN0TWVkaWESHy5ibG9ja25pbmphLnYxLkxpc3RNZWRpYVJlcXVlc3QaIC5ibG9ja25pbmphLnYxLkxpc3RNZWRpYVJlc3BvbnNlEksKCEdldE1lZGlhEh4uYmxvY2tuaW5qYS52MS5HZXRNZWRpYVJlcXVlc3QaHy5ibG9ja25pbmphLnYxLkdldE1lZGlhUmVzcG9uc2USVAoLVXBkYXRlTWVkaWESIS5ibG9ja25pbmphLnYxLlVwZGF0ZU1lZGlhUmVxdWVzdBoiLmJsb2NrbmluamEudjEuVXBkYXRlTWVkaWFSZXNwb25zZRJUCgtEZWxldGVNZWRpYRIhLmJsb2NrbmluamEudjEuRGVsZXRlTWVkaWFSZXF1ZXN0GiIuYmxvY2tuaW5qYS52MS5EZWxldGVNZWRpYVJlc3BvbnNlEloKDUdldE1lZGlhVXNhZ2USIy5ibG9ja25pbmphLnYxLkdldE1lZGlhVXNhZ2VSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5HZXRNZWRpYVVzYWdlUmVzcG9uc2USbAoTR2VuZXJhdGVVcGxvYWRUb2tlbhIpLmJsb2NrbmluamEudjEuR2VuZXJhdGVVcGxvYWRUb2tlblJlcXVlc3QaKi5ibG9ja25pbmphLnYxLkdlbmVyYXRlVXBsb2FkVG9rZW5SZXNwb25zZRJUCgtMaXN0Rm9sZGVycxIhLmJsb2NrbmluamEudjEuTGlzdEZvbGRlcnNSZXF1ZXN0GiIuYmxvY2tuaW5qYS52MS5MaXN0Rm9sZGVyc1Jlc3BvbnNlElcKDENyZWF0ZUZvbGRlchIiLmJsb2NrbmluamEudjEuQ3JlYXRlRm9sZGVyUmVxdWVzdBojLmJsb2NrbmluamEudjEuQ3JlYXRlRm9sZGVyUmVzcG9uc2USVwoMVXBkYXRlRm9sZGVyEiIuYmxvY2tuaW5qYS52MS5VcGRhdGVGb2xkZXJSZXF1ZXN0GiMuYmxvY2tuaW5qYS52MS5VcGRhdGVGb2xkZXJSZXNwb25zZRJXCgxEZWxldGVGb2xkZXISIi5ibG9ja25pbmphLnYxLkRlbGV0ZUZvbGRlclJlcXVlc3QaIy5ibG9ja25pbmphLnYxLkRlbGV0ZUZvbGRlclJlc3BvbnNlEmAKD0J1bGtEZWxldGVNZWRpYRIlLmJsb2NrbmluamEudjEuQnVsa0RlbGV0ZU1lZGlhUmVxdWVzdBomLmJsb2NrbmluamEudjEuQnVsa0RlbGV0ZU1lZGlhUmVzcG9uc2USWgoNQnVsa01vdmVNZWRpYRIjLmJsb2NrbmluamEudjEuQnVsa01vdmVNZWRpYVJlcXVlc3QaJC5ibG9ja25pbmphLnYxLkJ1bGtNb3ZlTWVkaWFSZXNwb25zZRJXCgxCdWxrVGFnTWVkaWESIi5ibG9ja25pbmphLnYxLkJ1bGtUYWdNZWRpYVJlcXVlc3QaIy5ibG9ja25pbmphLnYxLkJ1bGtUYWdNZWRpYVJlc3BvbnNlQsABChFjb20uYmxvY2tuaW5qYS52MUIKTWVkaWFQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_blockninja_v1_common, file_google_protobuf_timestamp], +); +/** + * @generated from service blockninja.v1.MediaService + */ +const MediaService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_media, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/media.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * List all media with optional filtering + * + * @generated from rpc blockninja.v1.MediaService.ListMedia + */ +const listMedia = MediaService.method.listMedia; +/** + * Get a single media item + * + * @generated from rpc blockninja.v1.MediaService.GetMedia + */ +MediaService.method.getMedia; +/** + * Update media metadata (filename, alt text, folder) + * + * @generated from rpc blockninja.v1.MediaService.UpdateMedia + */ +MediaService.method.updateMedia; +/** + * Delete a media item + * + * @generated from rpc blockninja.v1.MediaService.DeleteMedia + */ +MediaService.method.deleteMedia; +/** + * Get media usage information + * + * @generated from rpc blockninja.v1.MediaService.GetMediaUsage + */ +MediaService.method.getMediaUsage; +/** + * Generate a one-time upload token for secure file uploads + * + * @generated from rpc blockninja.v1.MediaService.GenerateUploadToken + */ +const generateUploadToken = MediaService.method.generateUploadToken; +/** + * Folder management + * + * @generated from rpc blockninja.v1.MediaService.ListFolders + */ +const listFolders = MediaService.method.listFolders; +/** + * @generated from rpc blockninja.v1.MediaService.CreateFolder + */ +MediaService.method.createFolder; +/** + * @generated from rpc blockninja.v1.MediaService.UpdateFolder + */ +MediaService.method.updateFolder; +/** + * @generated from rpc blockninja.v1.MediaService.DeleteFolder + */ +MediaService.method.deleteFolder; +/** + * Bulk operations + * + * @generated from rpc blockninja.v1.MediaService.BulkDeleteMedia + */ +MediaService.method.bulkDeleteMedia; +/** + * @generated from rpc blockninja.v1.MediaService.BulkMoveMedia + */ +const bulkMoveMedia = MediaService.method.bulkMoveMedia; +/** + * @generated from rpc blockninja.v1.MediaService.BulkTagMedia + */ +MediaService.method.bulkTagMedia; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/media_moderation.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/media_moderation.proto. + */ +const file_blockninja_v1_media_moderation = /*@__PURE__*/ fileDesc( + "CiRibG9ja25pbmphL3YxL21lZGlhX21vZGVyYXRpb24ucHJvdG8SDWJsb2NrbmluamEudjEi7AMKE01vZGVyYXRpb25RdWV1ZUl0ZW0SCgoCaWQYASABKAkSEAoIbWVkaWFfaWQYAiABKAkSDgoGc3RhdHVzGAMgASgJEhUKDXRodW1ibmFpbF91cmwYBCABKAkSEAoIZmlsZW5hbWUYBSABKAkSEQoJbWltZV90eXBlGAYgASgJEhEKCWZpbGVfc2l6ZRgHIAEoAxIVCg1zb3VyY2VfcGx1Z2luGAggASgJEhMKC3NvdXJjZV90eXBlGAkgASgJEhUKDXNvdXJjZV9yZWZfaWQYCiABKAkSGAoQdXBsb2FkZXJfdXNlcl9pZBgLIAEoCRIdChV1cGxvYWRlcl9kaXNwbGF5X25hbWUYDCABKAkSNAoLc2FmZV9zZWFyY2gYDSABKAsyHy5ibG9ja25pbmphLnYxLlNhZmVTZWFyY2hTY29yZXMSLgoKdG9wX2xhYmVscxgOIAMoCzIaLmJsb2NrbmluamEudjEuVmlzaW9uTGFiZWwSLgoKY3JlYXRlZF9hdBgPIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASMAoMbW9kZXJhdGVkX2F0GBAgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIUCgxjb250ZW50X2hhc2gYESABKAkiYQoQU2FmZVNlYXJjaFNjb3JlcxINCgVhZHVsdBgBIAEoCRIQCgh2aW9sZW5jZRgCIAEoCRIMCgRyYWN5GAMgASgJEg8KB21lZGljYWwYBCABKAkSDQoFc3Bvb2YYBSABKAkiMQoLVmlzaW9uTGFiZWwSEwoLZGVzY3JpcHRpb24YASABKAkSDQoFc2NvcmUYAiABKAIiggkKEE1vZGVyYXRpb25EZXRhaWwSCgoCaWQYASABKAkSEAoIbWVkaWFfaWQYAiABKAkSDgoGc3RhdHVzGAMgASgJEhUKDW9yaWdpbmFsX3BhdGgYBCABKAkSEAoIZmlsZW5hbWUYBSABKAkSEQoJbWltZV90eXBlGAYgASgJEhEKCWZpbGVfc2l6ZRgHIAEoAxI0CgtzYWZlX3NlYXJjaBgIIAEoCzIfLmJsb2NrbmluamEudjEuU2FmZVNlYXJjaFNjb3JlcxIqCgZsYWJlbHMYCSADKAsyGi5ibG9ja25pbmphLnYxLlZpc2lvbkxhYmVsEhQKDGNvbnRlbnRfaGFzaBgKIAEoCRIZChFwaG90b2RuYV9pc19tYXRjaBgLIAEoCBIcChRwaG90b2RuYV9tYXRjaF9zY29yZRgMIAEoAhI3ChNwaG90b2RuYV9zY2FubmVkX2F0GA0gASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIVCg1zb3VyY2VfcGx1Z2luGA4gASgJEhMKC3NvdXJjZV90eXBlGA8gASgJEhUKDXNvdXJjZV9yZWZfaWQYECABKAkSGAoQdXBsb2FkZXJfdXNlcl9pZBgRIAEoCRIdChV1cGxvYWRlcl9kaXNwbGF5X25hbWUYEiABKAkSEQoJdXBsb2FkX2lwGBMgASgJEhkKEXVwbG9hZF91c2VyX2FnZW50GBQgASgJEhgKEGV4aWZfY2FtZXJhX21ha2UYFSABKAkSGQoRZXhpZl9jYW1lcmFfbW9kZWwYFiABKAkSFQoNZXhpZl9zb2Z0d2FyZRgXIAEoCRI6ChZleGlmX2RhdGV0aW1lX29yaWdpbmFsGBggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIZCgxleGlmX2dwc19sYXQYGSABKAFIAIgBARIZCgxleGlmX2dwc19sbmcYGiABKAFIAYgBARIXCgpkZXZpY2VfbGF0GBsgASgBSAKIAQESFwoKZGV2aWNlX2xuZxgcIAEoAUgDiAEBEh4KEWRldmljZV9hY2N1cmFjeV9tGB0gASgBSASIAQESFAoMbW9kZXJhdGVkX2J5GB4gASgJEjAKDG1vZGVyYXRlZF9hdBgfIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFwoPbW9kZXJhdGlvbl9ub3RlGCAgASgJEjYKC2xvZ19lbnRyaWVzGCEgAygLMiEuYmxvY2tuaW5qYS52MS5Nb2RlcmF0aW9uTG9nRW50cnkSLgoKY3JlYXRlZF9hdBgiIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKdXBkYXRlZF9hdBgjIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCDwoNX2V4aWZfZ3BzX2xhdEIPCg1fZXhpZl9ncHNfbG5nQg0KC19kZXZpY2VfbGF0Qg0KC19kZXZpY2VfbG5nQhQKEl9kZXZpY2VfYWNjdXJhY3lfbSKgAQoSTW9kZXJhdGlvbkxvZ0VudHJ5EgoKAmlkGAEgASgJEg4KBmFjdGlvbhgCIAEoCRIUCgxwZXJmb3JtZWRfYnkYAyABKAkSGgoScGVyZm9ybWVkX2J5X2VtYWlsGAQgASgJEgwKBG5vdGUYBSABKAkSLgoKY3JlYXRlZF9hdBgGIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAi5wIKGkxpc3RNb2RlcmF0aW9uUXVldWVSZXF1ZXN0Ei0KCnBhZ2luYXRpb24YASABKAsyGS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb24SGgoNc3RhdHVzX2ZpbHRlchgCIAEoCUgAiAEBEiEKFHNvdXJjZV9wbHVnaW5fZmlsdGVyGAMgASgJSAGIAQESMgoJZGF0ZV9mcm9tGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgCiAEBEjAKB2RhdGVfdG8YBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSAOIAQESHAoPdXBsb2FkZXJfc2VhcmNoGAYgASgJSASIAQFCEAoOX3N0YXR1c19maWx0ZXJCFwoVX3NvdXJjZV9wbHVnaW5fZmlsdGVyQgwKCl9kYXRlX2Zyb21CCgoIX2RhdGVfdG9CEgoQX3VwbG9hZGVyX3NlYXJjaCKHAQobTGlzdE1vZGVyYXRpb25RdWV1ZVJlc3BvbnNlEjEKBWl0ZW1zGAEgAygLMiIuYmxvY2tuaW5qYS52MS5Nb2RlcmF0aW9uUXVldWVJdGVtEjUKCnBhZ2luYXRpb24YAiABKAsyIS5ibG9ja25pbmphLnYxLlBhZ2luYXRpb25SZXNwb25zZSI3ChpHZXRNb2RlcmF0aW9uRGV0YWlsUmVxdWVzdBIZChFtZWRpYV9hbmFseXNpc19pZBgBIAEoCSJOChtHZXRNb2RlcmF0aW9uRGV0YWlsUmVzcG9uc2USLwoGZGV0YWlsGAEgASgLMh8uYmxvY2tuaW5qYS52MS5Nb2RlcmF0aW9uRGV0YWlsIl0KFE1vZGVyYXRlTWVkaWFSZXF1ZXN0EhkKEW1lZGlhX2FuYWx5c2lzX2lkGAEgASgJEg4KBmFjdGlvbhgCIAEoCRIRCgRub3RlGAMgASgJSACIAQFCBwoFX25vdGUiSAoVTW9kZXJhdGVNZWRpYVJlc3BvbnNlEi8KBmRldGFpbBgBIAEoCzIfLmJsb2NrbmluamEudjEuTW9kZXJhdGlvbkRldGFpbCJiChhCdWxrTW9kZXJhdGVNZWRpYVJlcXVlc3QSGgoSbWVkaWFfYW5hbHlzaXNfaWRzGAEgAygJEg4KBmFjdGlvbhgCIAEoCRIRCgRub3RlGAMgASgJSACIAQFCBwoFX25vdGUiSwoZQnVsa01vZGVyYXRlTWVkaWFSZXNwb25zZRIXCg9wcm9jZXNzZWRfY291bnQYASABKAUSFQoNc2tpcHBlZF9jb3VudBgCIAEoBSI5ChxSZXF1ZXN0RXZpZGVuY2VFeHBvcnRSZXF1ZXN0EhkKEW1lZGlhX2FuYWx5c2lzX2lkGAEgASgJIlEKHVJlcXVlc3RFdmlkZW5jZUV4cG9ydFJlc3BvbnNlEhQKDGRvd25sb2FkX3VybBgBIAEoCRIaChJleHBpcmVzX2luX3NlY29uZHMYAiABKAMiGwoZR2V0TW9kZXJhdGlvblN0YXRzUmVxdWVzdCKoAgoaR2V0TW9kZXJhdGlvblN0YXRzUmVzcG9uc2USFgoOcGVuZGluZ19yZXZpZXcYASABKAUSFAoMcGVuZGluZ19zY2FuGAIgASgFEhMKC3F1YXJhbnRpbmVkGAMgASgFEhgKEHF1YXJhbnRpbmVkX2NzYW0YBCABKAUSFQoNYXV0b19hcHByb3ZlZBgFIAEoBRIQCghhcHByb3ZlZBgGIAEoBRIQCghyZWplY3RlZBgHIAEoBRIVCg1ldmlkZW5jZV9oZWxkGAggASgFEhkKEWV2aWRlbmNlX2V4cG9ydGVkGAkgASgFEhcKD2FuYWx5c2lzX2ZhaWxlZBgKIAEoBRIYChBjc2FtX3NjYW5fZmFpbGVkGAsgASgFEg0KBXRvdGFsGAwgASgFMpcFChZNZWRpYU1vZGVyYXRpb25TZXJ2aWNlEmwKE0xpc3RNb2RlcmF0aW9uUXVldWUSKS5ibG9ja25pbmphLnYxLkxpc3RNb2RlcmF0aW9uUXVldWVSZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5MaXN0TW9kZXJhdGlvblF1ZXVlUmVzcG9uc2USbAoTR2V0TW9kZXJhdGlvbkRldGFpbBIpLmJsb2NrbmluamEudjEuR2V0TW9kZXJhdGlvbkRldGFpbFJlcXVlc3QaKi5ibG9ja25pbmphLnYxLkdldE1vZGVyYXRpb25EZXRhaWxSZXNwb25zZRJaCg1Nb2RlcmF0ZU1lZGlhEiMuYmxvY2tuaW5qYS52MS5Nb2RlcmF0ZU1lZGlhUmVxdWVzdBokLmJsb2NrbmluamEudjEuTW9kZXJhdGVNZWRpYVJlc3BvbnNlEmYKEUJ1bGtNb2RlcmF0ZU1lZGlhEicuYmxvY2tuaW5qYS52MS5CdWxrTW9kZXJhdGVNZWRpYVJlcXVlc3QaKC5ibG9ja25pbmphLnYxLkJ1bGtNb2RlcmF0ZU1lZGlhUmVzcG9uc2UScgoVUmVxdWVzdEV2aWRlbmNlRXhwb3J0EisuYmxvY2tuaW5qYS52MS5SZXF1ZXN0RXZpZGVuY2VFeHBvcnRSZXF1ZXN0GiwuYmxvY2tuaW5qYS52MS5SZXF1ZXN0RXZpZGVuY2VFeHBvcnRSZXNwb25zZRJpChJHZXRNb2RlcmF0aW9uU3RhdHMSKC5ibG9ja25pbmphLnYxLkdldE1vZGVyYXRpb25TdGF0c1JlcXVlc3QaKS5ibG9ja25pbmphLnYxLkdldE1vZGVyYXRpb25TdGF0c1Jlc3BvbnNlQsoBChFjb20uYmxvY2tuaW5qYS52MUIUTWVkaWFNb2RlcmF0aW9uUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_blockninja_v1_common, file_google_protobuf_timestamp], +); +/** + * @generated from service blockninja.v1.MediaModerationService + */ +const MediaModerationService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_media_moderation, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/media_moderation.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * @generated from rpc blockninja.v1.MediaModerationService.ListModerationQueue + */ +MediaModerationService.method.listModerationQueue; +/** + * @generated from rpc blockninja.v1.MediaModerationService.GetModerationDetail + */ +MediaModerationService.method.getModerationDetail; +/** + * @generated from rpc blockninja.v1.MediaModerationService.ModerateMedia + */ +MediaModerationService.method.moderateMedia; +/** + * @generated from rpc blockninja.v1.MediaModerationService.BulkModerateMedia + */ +MediaModerationService.method.bulkModerateMedia; +/** + * @generated from rpc blockninja.v1.MediaModerationService.RequestEvidenceExport + */ +MediaModerationService.method.requestEvidenceExport; +/** + * @generated from rpc blockninja.v1.MediaModerationService.GetModerationStats + */ +MediaModerationService.method.getModerationStats; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/menus.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/menus.proto. + */ +const file_blockninja_v1_menus = /*@__PURE__*/ fileDesc( + "ChlibG9ja25pbmphL3YxL21lbnVzLnByb3RvEg1ibG9ja25pbmphLnYxIoABCgRNZW51EgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSLgoKY3JlYXRlZF9hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKdXBkYXRlZF9hdBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAi6wQKCE1lbnVJdGVtEgoKAmlkGAEgASgJEg8KB21lbnVfaWQYAiABKAkSDQoFbGFiZWwYAyABKAkSEAoDdXJsGAQgASgJSACIAQESFAoHcGFnZV9pZBgFIAEoCUgBiAEBEhYKCXBhcmVudF9pZBgGIAEoCUgCiAEBEhIKCnNvcnRfb3JkZXIYByABKAUSFwoPb3Blbl9pbl9uZXdfdGFiGAggASgIEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhcKCnBhZ2VfdGl0bGUYCiABKAlIA4gBARIWCglwYWdlX3NsdWcYCyABKAlIBIgBARISCgV0aXRsZRgMIAEoCUgFiAEBEhYKCWNzc19jbGFzcxgNIAEoCUgGiAEBEhEKCWl0ZW1fdHlwZRgOIAEoCRIUCgxjb2x1bW5fZ3JvdXAYDyABKAUSGAoLZGVzY3JpcHRpb24YECABKAlIB4gBARISCgVpbWFnZRgRIAEoCUgIiAEBEhkKDG1lZ2FfY29udGVudBgSIAEoCUgJiAEBEikKCGNoaWxkcmVuGBMgAygLMhcuYmxvY2tuaW5qYS52MS5NZW51SXRlbRIRCgRpY29uGBQgASgJSAqIAQFCBgoEX3VybEIKCghfcGFnZV9pZEIMCgpfcGFyZW50X2lkQg0KC19wYWdlX3RpdGxlQgwKCl9wYWdlX3NsdWdCCAoGX3RpdGxlQgwKCl9jc3NfY2xhc3NCDgoMX2Rlc2NyaXB0aW9uQggKBl9pbWFnZUIPCg1fbWVnYV9jb250ZW50QgcKBV9pY29uIhIKEExpc3RNZW51c1JlcXVlc3QiNwoRTGlzdE1lbnVzUmVzcG9uc2USIgoFbWVudXMYASADKAsyEy5ibG9ja25pbmphLnYxLk1lbnUiHAoOR2V0TWVudVJlcXVlc3QSCgoCaWQYASABKAkiNAoPR2V0TWVudVJlc3BvbnNlEiEKBG1lbnUYASABKAsyEy5ibG9ja25pbmphLnYxLk1lbnUiJAoUR2V0TWVudUJ5TmFtZVJlcXVlc3QSDAoEbmFtZRgBIAEoCSI6ChVHZXRNZW51QnlOYW1lUmVzcG9uc2USIQoEbWVudRgBIAEoCzITLmJsb2NrbmluamEudjEuTWVudSIhChFDcmVhdGVNZW51UmVxdWVzdBIMCgRuYW1lGAEgASgJIjcKEkNyZWF0ZU1lbnVSZXNwb25zZRIhCgRtZW51GAEgASgLMhMuYmxvY2tuaW5qYS52MS5NZW51Ii0KEVVwZGF0ZU1lbnVSZXF1ZXN0EgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkiNwoSVXBkYXRlTWVudVJlc3BvbnNlEiEKBG1lbnUYASABKAsyEy5ibG9ja25pbmphLnYxLk1lbnUiHwoRRGVsZXRlTWVudVJlcXVlc3QSCgoCaWQYASABKAkiFAoSRGVsZXRlTWVudVJlc3BvbnNlIiYKE0dldE1lbnVJdGVtc1JlcXVlc3QSDwoHbWVudV9pZBgBIAEoCSI+ChRHZXRNZW51SXRlbXNSZXNwb25zZRImCgVpdGVtcxgBIAMoCzIXLmJsb2NrbmluamEudjEuTWVudUl0ZW0i1AMKEkFkZE1lbnVJdGVtUmVxdWVzdBIPCgdtZW51X2lkGAEgASgJEg0KBWxhYmVsGAIgASgJEhAKA3VybBgDIAEoCUgAiAEBEhQKB3BhZ2VfaWQYBCABKAlIAYgBARIWCglwYXJlbnRfaWQYBSABKAlIAogBARIXCgpzb3J0X29yZGVyGAYgASgFSAOIAQESFwoPb3Blbl9pbl9uZXdfdGFiGAcgASgIEhIKBXRpdGxlGAggASgJSASIAQESFgoJY3NzX2NsYXNzGAkgASgJSAWIAQESEQoJaXRlbV90eXBlGAogASgJEhQKDGNvbHVtbl9ncm91cBgLIAEoBRIYCgtkZXNjcmlwdGlvbhgMIAEoCUgGiAEBEhIKBWltYWdlGA0gASgJSAeIAQESGQoMbWVnYV9jb250ZW50GA4gASgJSAiIAQESEQoEaWNvbhgPIAEoCUgJiAEBQgYKBF91cmxCCgoIX3BhZ2VfaWRCDAoKX3BhcmVudF9pZEINCgtfc29ydF9vcmRlckIICgZfdGl0bGVCDAoKX2Nzc19jbGFzc0IOCgxfZGVzY3JpcHRpb25CCAoGX2ltYWdlQg8KDV9tZWdhX2NvbnRlbnRCBwoFX2ljb24iPAoTQWRkTWVudUl0ZW1SZXNwb25zZRIlCgRpdGVtGAEgASgLMhcuYmxvY2tuaW5qYS52MS5NZW51SXRlbSK+AwoVVXBkYXRlTWVudUl0ZW1SZXF1ZXN0EgoKAmlkGAEgASgJEg0KBWxhYmVsGAIgASgJEhAKA3VybBgDIAEoCUgAiAEBEhQKB3BhZ2VfaWQYBCABKAlIAYgBARIWCglwYXJlbnRfaWQYBSABKAlIAogBARISCgpzb3J0X29yZGVyGAYgASgFEhcKD29wZW5faW5fbmV3X3RhYhgHIAEoCBISCgV0aXRsZRgIIAEoCUgDiAEBEhYKCWNzc19jbGFzcxgJIAEoCUgEiAEBEhEKCWl0ZW1fdHlwZRgKIAEoCRIUCgxjb2x1bW5fZ3JvdXAYCyABKAUSGAoLZGVzY3JpcHRpb24YDCABKAlIBYgBARISCgVpbWFnZRgNIAEoCUgGiAEBEhkKDG1lZ2FfY29udGVudBgOIAEoCUgHiAEBEhEKBGljb24YDyABKAlICIgBAUIGCgRfdXJsQgoKCF9wYWdlX2lkQgwKCl9wYXJlbnRfaWRCCAoGX3RpdGxlQgwKCl9jc3NfY2xhc3NCDgoMX2Rlc2NyaXB0aW9uQggKBl9pbWFnZUIPCg1fbWVnYV9jb250ZW50QgcKBV9pY29uIj8KFlVwZGF0ZU1lbnVJdGVtUmVzcG9uc2USJQoEaXRlbRgBIAEoCzIXLmJsb2NrbmluamEudjEuTWVudUl0ZW0iIwoVRGVsZXRlTWVudUl0ZW1SZXF1ZXN0EgoKAmlkGAEgASgJIhgKFkRlbGV0ZU1lbnVJdGVtUmVzcG9uc2UiPAoXUmVvcmRlck1lbnVJdGVtc1JlcXVlc3QSDwoHbWVudV9pZBgBIAEoCRIQCghpdGVtX2lkcxgCIAMoCSJCChhSZW9yZGVyTWVudUl0ZW1zUmVzcG9uc2USJgoFaXRlbXMYASADKAsyFy5ibG9ja25pbmphLnYxLk1lbnVJdGVtIioKF0dldE1lbnVJdGVtc1RyZWVSZXF1ZXN0Eg8KB21lbnVfaWQYASABKAkiQgoYR2V0TWVudUl0ZW1zVHJlZVJlc3BvbnNlEiYKBWl0ZW1zGAEgAygLMhcuYmxvY2tuaW5qYS52MS5NZW51SXRlbSIjChRGZXRjaFVybFRpdGxlUmVxdWVzdBILCgN1cmwYASABKAkiJgoVRmV0Y2hVcmxUaXRsZVJlc3BvbnNlEg0KBXRpdGxlGAEgASgJIjsKFER1cGxpY2F0ZU1lbnVSZXF1ZXN0EhEKCXNvdXJjZV9pZBgBIAEoCRIQCghuZXdfbmFtZRgCIAEoCSJQChVEdXBsaWNhdGVNZW51UmVzcG9uc2USIQoEbWVudRgBIAEoCzITLmJsb2NrbmluamEudjEuTWVudRIUCgxpdGVtc19jb3BpZWQYAiABKAUy7AkKDE1lbnVzU2VydmljZRJOCglMaXN0TWVudXMSHy5ibG9ja25pbmphLnYxLkxpc3RNZW51c1JlcXVlc3QaIC5ibG9ja25pbmphLnYxLkxpc3RNZW51c1Jlc3BvbnNlEkgKB0dldE1lbnUSHS5ibG9ja25pbmphLnYxLkdldE1lbnVSZXF1ZXN0Gh4uYmxvY2tuaW5qYS52MS5HZXRNZW51UmVzcG9uc2USWgoNR2V0TWVudUJ5TmFtZRIjLmJsb2NrbmluamEudjEuR2V0TWVudUJ5TmFtZVJlcXVlc3QaJC5ibG9ja25pbmphLnYxLkdldE1lbnVCeU5hbWVSZXNwb25zZRJRCgpDcmVhdGVNZW51EiAuYmxvY2tuaW5qYS52MS5DcmVhdGVNZW51UmVxdWVzdBohLmJsb2NrbmluamEudjEuQ3JlYXRlTWVudVJlc3BvbnNlElEKClVwZGF0ZU1lbnUSIC5ibG9ja25pbmphLnYxLlVwZGF0ZU1lbnVSZXF1ZXN0GiEuYmxvY2tuaW5qYS52MS5VcGRhdGVNZW51UmVzcG9uc2USUQoKRGVsZXRlTWVudRIgLmJsb2NrbmluamEudjEuRGVsZXRlTWVudVJlcXVlc3QaIS5ibG9ja25pbmphLnYxLkRlbGV0ZU1lbnVSZXNwb25zZRJXCgxHZXRNZW51SXRlbXMSIi5ibG9ja25pbmphLnYxLkdldE1lbnVJdGVtc1JlcXVlc3QaIy5ibG9ja25pbmphLnYxLkdldE1lbnVJdGVtc1Jlc3BvbnNlElQKC0FkZE1lbnVJdGVtEiEuYmxvY2tuaW5qYS52MS5BZGRNZW51SXRlbVJlcXVlc3QaIi5ibG9ja25pbmphLnYxLkFkZE1lbnVJdGVtUmVzcG9uc2USXQoOVXBkYXRlTWVudUl0ZW0SJC5ibG9ja25pbmphLnYxLlVwZGF0ZU1lbnVJdGVtUmVxdWVzdBolLmJsb2NrbmluamEudjEuVXBkYXRlTWVudUl0ZW1SZXNwb25zZRJdCg5EZWxldGVNZW51SXRlbRIkLmJsb2NrbmluamEudjEuRGVsZXRlTWVudUl0ZW1SZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5EZWxldGVNZW51SXRlbVJlc3BvbnNlEmMKEFJlb3JkZXJNZW51SXRlbXMSJi5ibG9ja25pbmphLnYxLlJlb3JkZXJNZW51SXRlbXNSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5SZW9yZGVyTWVudUl0ZW1zUmVzcG9uc2USYwoQR2V0TWVudUl0ZW1zVHJlZRImLmJsb2NrbmluamEudjEuR2V0TWVudUl0ZW1zVHJlZVJlcXVlc3QaJy5ibG9ja25pbmphLnYxLkdldE1lbnVJdGVtc1RyZWVSZXNwb25zZRJaCg1GZXRjaFVybFRpdGxlEiMuYmxvY2tuaW5qYS52MS5GZXRjaFVybFRpdGxlUmVxdWVzdBokLmJsb2NrbmluamEudjEuRmV0Y2hVcmxUaXRsZVJlc3BvbnNlEloKDUR1cGxpY2F0ZU1lbnUSIy5ibG9ja25pbmphLnYxLkR1cGxpY2F0ZU1lbnVSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5EdXBsaWNhdGVNZW51UmVzcG9uc2VCwAEKEWNvbS5ibG9ja25pbmphLnYxQgpNZW51c1Byb3RvUAFaSmdpdC5kZXYuYWxleGR1bm1vdy5jb20vYmxvY2svbmluamEvaW50ZXJuYWwvYXBpL2Jsb2NrbmluamEvdjE7YmxvY2tuaW5qYXYxogIDQlhYqgINQmxvY2tuaW5qYS5WMcoCDUJsb2NrbmluamFcVjHiAhlCbG9ja25pbmphXFYxXEdQQk1ldGFkYXRh6gIOQmxvY2tuaW5qYTo6VjFiBnByb3RvMw", + [file_google_protobuf_timestamp], +); +/** + * MenusService handles menu management + * + * @generated from service blockninja.v1.MenusService + */ +const MenusService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_menus, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/menus.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * List all menus + * + * @generated from rpc blockninja.v1.MenusService.ListMenus + */ +MenusService.method.listMenus; +/** + * Get a single menu by ID + * + * @generated from rpc blockninja.v1.MenusService.GetMenu + */ +MenusService.method.getMenu; +/** + * Get a menu by name + * + * @generated from rpc blockninja.v1.MenusService.GetMenuByName + */ +MenusService.method.getMenuByName; +/** + * Create a new menu + * + * @generated from rpc blockninja.v1.MenusService.CreateMenu + */ +MenusService.method.createMenu; +/** + * Update a menu + * + * @generated from rpc blockninja.v1.MenusService.UpdateMenu + */ +MenusService.method.updateMenu; +/** + * Delete a menu + * + * @generated from rpc blockninja.v1.MenusService.DeleteMenu + */ +MenusService.method.deleteMenu; +/** + * Get menu items for a menu + * + * @generated from rpc blockninja.v1.MenusService.GetMenuItems + */ +MenusService.method.getMenuItems; +/** + * Add a menu item + * + * @generated from rpc blockninja.v1.MenusService.AddMenuItem + */ +MenusService.method.addMenuItem; +/** + * Update a menu item + * + * @generated from rpc blockninja.v1.MenusService.UpdateMenuItem + */ +MenusService.method.updateMenuItem; +/** + * Delete a menu item + * + * @generated from rpc blockninja.v1.MenusService.DeleteMenuItem + */ +MenusService.method.deleteMenuItem; +/** + * Reorder menu items + * + * @generated from rpc blockninja.v1.MenusService.ReorderMenuItems + */ +MenusService.method.reorderMenuItems; +/** + * Get menu items as a tree (with children populated) + * + * @generated from rpc blockninja.v1.MenusService.GetMenuItemsTree + */ +MenusService.method.getMenuItemsTree; +/** + * Fetch title from a URL (for auto-filling menu item labels) + * + * @generated from rpc blockninja.v1.MenusService.FetchUrlTitle + */ +MenusService.method.fetchUrlTitle; +/** + * Duplicate a menu with all its items + * + * @generated from rpc blockninja.v1.MenusService.DuplicateMenu + */ +MenusService.method.duplicateMenu; + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/pages.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * @generated from rpc blockninja.v1.PagesService.ListPages + */ +PagesService.method.listPages; +/** + * @generated from rpc blockninja.v1.PagesService.GetPage + */ +PagesService.method.getPage; +/** + * @generated from rpc blockninja.v1.PagesService.CreatePage + */ +PagesService.method.createPage; +/** + * @generated from rpc blockninja.v1.PagesService.UpdatePage + */ +PagesService.method.updatePage; +/** + * @generated from rpc blockninja.v1.PagesService.DeletePage + */ +PagesService.method.deletePage; +/** + * @generated from rpc blockninja.v1.PagesService.PublishPage + */ +PagesService.method.publishPage; +/** + * @generated from rpc blockninja.v1.PagesService.UnpublishPage + */ +PagesService.method.unpublishPage; +/** + * @generated from rpc blockninja.v1.PagesService.ListPageVersions + */ +PagesService.method.listPageVersions; +/** + * @generated from rpc blockninja.v1.PagesService.RollbackPage + */ +PagesService.method.rollbackPage; +/** + * @generated from rpc blockninja.v1.PagesService.UpdatePageVersionName + */ +PagesService.method.updatePageVersionName; +/** + * @generated from rpc blockninja.v1.PagesService.ListTemplates + */ +PagesService.method.listTemplates; +/** + * System pages + * + * @generated from rpc blockninja.v1.PagesService.ListSystemPages + */ +PagesService.method.listSystemPages; +/** + * @generated from rpc blockninja.v1.PagesService.ResetSystemPage + */ +PagesService.method.resetSystemPage; +/** + * Draft management + * + * @generated from rpc blockninja.v1.PagesService.DiscardDraftChanges + */ +PagesService.method.discardDraftChanges; +/** + * Bulk operations + * + * @generated from rpc blockninja.v1.PagesService.BulkDeletePages + */ +PagesService.method.bulkDeletePages; +/** + * @generated from rpc blockninja.v1.PagesService.BulkMovePages + */ +PagesService.method.bulkMovePages; +/** + * @generated from rpc blockninja.v1.PagesService.BulkPublishPages + */ +PagesService.method.bulkPublishPages; +/** + * Preview tokens + * + * @generated from rpc blockninja.v1.PagesService.GeneratePreviewToken + */ +PagesService.method.generatePreviewToken; +/** + * Scheduled publishing + * + * @generated from rpc blockninja.v1.PagesService.SchedulePage + */ +PagesService.method.schedulePage; +/** + * @generated from rpc blockninja.v1.PagesService.UnschedulePage + */ +PagesService.method.unschedulePage; +/** + * @generated from rpc blockninja.v1.PagesService.ListScheduledPages + */ +PagesService.method.listScheduledPages; +/** + * Master pages + * + * @generated from rpc blockninja.v1.PagesService.ListMasterPages + */ +PagesService.method.listMasterPages; +/** + * @generated from rpc blockninja.v1.PagesService.CreateMasterPage + */ +PagesService.method.createMasterPage; +/** + * @generated from rpc blockninja.v1.PagesService.DuplicateMasterPage + */ +PagesService.method.duplicateMasterPage; +/** + * @generated from rpc blockninja.v1.PagesService.SetDefaultMaster + */ +PagesService.method.setDefaultMaster; +/** + * @generated from rpc blockninja.v1.PagesService.GetDefaultMaster + */ +PagesService.method.getDefaultMaster; +/** + * @generated from rpc blockninja.v1.PagesService.SetPageMaster + */ +PagesService.method.setPageMaster; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/plugins.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/plugins.proto. + */ +const file_blockninja_v1_plugins = /*@__PURE__*/ fileDesc( + "ChtibG9ja25pbmphL3YxL3BsdWdpbnMucHJvdG8SDWJsb2NrbmluamEudjEizgYKClBsdWdpbkluZm8SDAoEbmFtZRgBIAEoCRIMCgRwYXRoGAIgASgJEisKBnN0YXR1cxgDIAEoDjIbLmJsb2NrbmluamEudjEuUGx1Z2luU3RhdHVzEhYKDmZhaWx1cmVfcmVhc29uGAQgASgJEi0KCWZhaWxlZF9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFQoNaGFzX3RlbXBsYXRlcxgGIAEoCBISCgpoYXNfYmxvY2tzGAcgASgIEhQKDGhhc19zZXR0aW5ncxgIIAEoCBIYChBoYXNfaHR0cF9oYW5kbGVyGAkgASgIEhgKEHN5c3RlbV90ZW1wbGF0ZXMYCiADKAkSFgoOcGFnZV90ZW1wbGF0ZXMYCyADKAkSEwoLYmxvY2tfdHlwZXMYDCADKAkSFAoMaXNfcHJvdGVjdGVkGA0gASgIEg8KB2VuYWJsZWQYDiABKAgSFwoPcGVuZGluZ19yZXN0YXJ0GA8gASgIEg4KBnNvdXJjZRgQIAEoCRIQCghyZXBvX3VybBgRIAEoCRIOCgZicmFuY2gYEiABKAkSEwoLY29tbWl0X2hhc2gYEyABKAkSMAoMaW5zdGFsbGVkX2F0GBQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIzCg9sYXN0X3VwZGF0ZWRfYXQYFSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhgKEHVwZGF0ZV9hdmFpbGFibGUYFiABKAgSHAoUbGF0ZXN0X3JlbW90ZV9jb21taXQYFyABKAkSNQoRdXBkYXRlX2NoZWNrZWRfYXQYGCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhAKCHNzaF9wb3J0GBkgASgFEhcKCnNzaF9rZXlfaWQYGiABKAlIAIgBARIZCgxzc2hfa2V5X25hbWUYGyABKAlIAYgBARIPCgd2ZXJzaW9uGBwgASgJEh0KFWxhdGVzdF9yZW1vdGVfdmVyc2lvbhgdIAEoCRIbChNkZXBlbmRlbmN5X3dhcm5pbmdzGB4gAygJQg0KC19zc2hfa2V5X2lkQg8KDV9zc2hfa2V5X25hbWUiFAoSTGlzdFBsdWdpbnNSZXF1ZXN0Io4BChNMaXN0UGx1Z2luc1Jlc3BvbnNlEioKB3BsdWdpbnMYASADKAsyGS5ibG9ja25pbmphLnYxLlBsdWdpbkluZm8SFAoMbG9hZGVkX2NvdW50GAIgASgFEhQKDGZhaWxlZF9jb3VudBgDIAEoBRIfChd1cGRhdGVzX2F2YWlsYWJsZV9jb3VudBgEIAEoBSIgChBHZXRQbHVnaW5SZXF1ZXN0EgwKBG5hbWUYASABKAkiPgoRR2V0UGx1Z2luUmVzcG9uc2USKQoGcGx1Z2luGAEgASgLMhkuYmxvY2tuaW5qYS52MS5QbHVnaW5JbmZvIiMKE0VuYWJsZVBsdWdpblJlcXVlc3QSDAoEbmFtZRgBIAEoCSI4ChRFbmFibGVQbHVnaW5SZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEg8KB21lc3NhZ2UYAiABKAkiJAoURGlzYWJsZVBsdWdpblJlcXVlc3QSDAoEbmFtZRgBIAEoCSI5ChVEaXNhYmxlUGx1Z2luUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCBIPCgdtZXNzYWdlGAIgASgJIiYKFlVuaW5zdGFsbFBsdWdpblJlcXVlc3QSDAoEbmFtZRgBIAEoCSI7ChdVbmluc3RhbGxQbHVnaW5SZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEg8KB21lc3NhZ2UYAiABKAkiWwoXU2F2ZVBsdWdpbkNvbmZpZ1JlcXVlc3QSDAoEbmFtZRgBIAEoCRIUCgdzc2hfa2V5GAIgASgJSACIAQESEAoIc3NoX3BvcnQYAyABKAVCCgoIX3NzaF9rZXkiKwoYU2F2ZVBsdWdpbkNvbmZpZ1Jlc3BvbnNlEg8KB3N1Y2Nlc3MYASABKAgihAEKFEluc3RhbGxQbHVnaW5SZXF1ZXN0EhAKCHJlcG9fdXJsGAEgASgJEhMKC3BsdWdpbl9uYW1lGAIgASgJEg4KBmJyYW5jaBgDIAEoCRIPCgdzc2hfa2V5GAQgASgJEhAKCHNzaF9wb3J0GAUgASgFEhIKCnNzaF9rZXlfaWQYBiABKAkiOQoVSW5zdGFsbFBsdWdpblJlc3BvbnNlEg8KB3N1Y2Nlc3MYASABKAgSDwoHbWVzc2FnZRgCIAEoCSJhChNVcGRhdGVQbHVnaW5SZXF1ZXN0EhMKC3BsdWdpbl9uYW1lGAEgASgJEg8KB3NzaF9rZXkYAiABKAkSEAoIc3NoX3BvcnQYAyABKAUSEgoKc3NoX2tleV9pZBgEIAEoCSI4ChRVcGRhdGVQbHVnaW5SZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEg8KB21lc3NhZ2UYAiABKAkiaQoPUmVzdGFydFByb2dyZXNzEgwKBHN0ZXAYASABKAkSDwoHbWVzc2FnZRgCIAEoCRIQCghpc19lcnJvchgDIAEoCBITCgtpc19jb21wbGV0ZRgEIAEoCBIQCghidWlsZF9pZBgFIAEoCSIVChNHZXREZXBsb3lLZXlSZXF1ZXN0IioKFEdldERlcGxveUtleVJlc3BvbnNlEhIKCnB1YmxpY19rZXkYASABKAkiGgoYR2VuZXJhdGVEZXBsb3lLZXlSZXF1ZXN0Ii8KGUdlbmVyYXRlRGVwbG95S2V5UmVzcG9uc2USEgoKcHVibGljX2tleRgBIAEoCSIXChVSZXF1ZXN0UmVzdGFydFJlcXVlc3QiQQoYR2V0UGx1Z2luQnVpbGRMb2dSZXF1ZXN0EhMKC3BsdWdpbl9uYW1lGAEgASgJEhAKCGxvZ190eXBlGAIgASgJIjwKGUdldFBsdWdpbkJ1aWxkTG9nUmVzcG9uc2USDwoHY29udGVudBgBIAEoCRIOCgZleGlzdHMYAiABKAgiGAoWTGlzdFBsdWdpblBhZ2VzUmVxdWVzdCJjChNQbHVnaW5BZG1pblBhZ2VJbmZvEhMKC3BsdWdpbl9uYW1lGAEgASgJEgsKA2tleRgCIAEoCRINCgV0aXRsZRgDIAEoCRIMCgRpY29uGAQgASgJEg0KBXJvdXRlGAUgASgJIkwKF0xpc3RQbHVnaW5QYWdlc1Jlc3BvbnNlEjEKBXBhZ2VzGAEgAygLMiIuYmxvY2tuaW5qYS52MS5QbHVnaW5BZG1pblBhZ2VJbmZvIooBCgZTU0hLZXkSCgoCaWQYASABKAkSDAoEbmFtZRgCIAEoCRIQCghzc2hfcG9ydBgDIAEoBRITCgtmaW5nZXJwcmludBgEIAEoCRISCgpjcmVhdGVkX2F0GAUgASgJEhIKCnVwZGF0ZWRfYXQYBiABKAkSFwoPdXNlZF9ieV9wbHVnaW5zGAcgAygJIhQKEkxpc3RTU0hLZXlzUmVxdWVzdCI6ChNMaXN0U1NIS2V5c1Jlc3BvbnNlEiMKBGtleXMYASADKAsyFS5ibG9ja25pbmphLnYxLlNTSEtleSJKChNDcmVhdGVTU0hLZXlSZXF1ZXN0EgwKBG5hbWUYASABKAkSEwoLcHJpdmF0ZV9rZXkYAiABKAkSEAoIc3NoX3BvcnQYAyABKAUiOgoUQ3JlYXRlU1NIS2V5UmVzcG9uc2USIgoDa2V5GAEgASgLMhUuYmxvY2tuaW5qYS52MS5TU0hLZXkiiwEKE1VwZGF0ZVNTSEtleVJlcXVlc3QSCgoCaWQYASABKAkSEQoEbmFtZRgCIAEoCUgAiAEBEhgKC3ByaXZhdGVfa2V5GAMgASgJSAGIAQESFQoIc3NoX3BvcnQYBCABKAVIAogBAUIHCgVfbmFtZUIOCgxfcHJpdmF0ZV9rZXlCCwoJX3NzaF9wb3J0IjoKFFVwZGF0ZVNTSEtleVJlc3BvbnNlEiIKA2tleRgBIAEoCzIVLmJsb2NrbmluamEudjEuU1NIS2V5IiEKE0RlbGV0ZVNTSEtleVJlcXVlc3QSCgoCaWQYASABKAkiOAoURGVsZXRlU1NIS2V5UmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCBIPCgdtZXNzYWdlGAIgASgJKr4BCgxQbHVnaW5TdGF0dXMSHQoZUExVR0lOX1NUQVRVU19VTlNQRUNJRklFRBAAEhgKFFBMVUdJTl9TVEFUVVNfTE9BREVEEAESGAoUUExVR0lOX1NUQVRVU19GQUlMRUQQAhIaChZQTFVHSU5fU1RBVFVTX0RJU0FCTEVEEAMSHAoYUExVR0lOX1NUQVRVU19SRUJVSUxESU5HEAQSIQodUExVR0lOX1NUQVRVU19QRU5ESU5HX1JFU1RBUlQQBTKtDAoOUGx1Z2luc1NlcnZpY2USVAoLTGlzdFBsdWdpbnMSIS5ibG9ja25pbmphLnYxLkxpc3RQbHVnaW5zUmVxdWVzdBoiLmJsb2NrbmluamEudjEuTGlzdFBsdWdpbnNSZXNwb25zZRJOCglHZXRQbHVnaW4SHy5ibG9ja25pbmphLnYxLkdldFBsdWdpblJlcXVlc3QaIC5ibG9ja25pbmphLnYxLkdldFBsdWdpblJlc3BvbnNlElcKDEVuYWJsZVBsdWdpbhIiLmJsb2NrbmluamEudjEuRW5hYmxlUGx1Z2luUmVxdWVzdBojLmJsb2NrbmluamEudjEuRW5hYmxlUGx1Z2luUmVzcG9uc2USWgoNRGlzYWJsZVBsdWdpbhIjLmJsb2NrbmluamEudjEuRGlzYWJsZVBsdWdpblJlcXVlc3QaJC5ibG9ja25pbmphLnYxLkRpc2FibGVQbHVnaW5SZXNwb25zZRJgCg9Vbmluc3RhbGxQbHVnaW4SJS5ibG9ja25pbmphLnYxLlVuaW5zdGFsbFBsdWdpblJlcXVlc3QaJi5ibG9ja25pbmphLnYxLlVuaW5zdGFsbFBsdWdpblJlc3BvbnNlEmMKEFNhdmVQbHVnaW5Db25maWcSJi5ibG9ja25pbmphLnYxLlNhdmVQbHVnaW5Db25maWdSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5TYXZlUGx1Z2luQ29uZmlnUmVzcG9uc2USWgoNSW5zdGFsbFBsdWdpbhIjLmJsb2NrbmluamEudjEuSW5zdGFsbFBsdWdpblJlcXVlc3QaJC5ibG9ja25pbmphLnYxLkluc3RhbGxQbHVnaW5SZXNwb25zZRJXCgxVcGRhdGVQbHVnaW4SIi5ibG9ja25pbmphLnYxLlVwZGF0ZVBsdWdpblJlcXVlc3QaIy5ibG9ja25pbmphLnYxLlVwZGF0ZVBsdWdpblJlc3BvbnNlElcKDEdldERlcGxveUtleRIiLmJsb2NrbmluamEudjEuR2V0RGVwbG95S2V5UmVxdWVzdBojLmJsb2NrbmluamEudjEuR2V0RGVwbG95S2V5UmVzcG9uc2USZgoRR2VuZXJhdGVEZXBsb3lLZXkSJy5ibG9ja25pbmphLnYxLkdlbmVyYXRlRGVwbG95S2V5UmVxdWVzdBooLmJsb2NrbmluamEudjEuR2VuZXJhdGVEZXBsb3lLZXlSZXNwb25zZRJYCg5SZXF1ZXN0UmVzdGFydBIkLmJsb2NrbmluamEudjEuUmVxdWVzdFJlc3RhcnRSZXF1ZXN0Gh4uYmxvY2tuaW5qYS52MS5SZXN0YXJ0UHJvZ3Jlc3MwARJmChFHZXRQbHVnaW5CdWlsZExvZxInLmJsb2NrbmluamEudjEuR2V0UGx1Z2luQnVpbGRMb2dSZXF1ZXN0GiguYmxvY2tuaW5qYS52MS5HZXRQbHVnaW5CdWlsZExvZ1Jlc3BvbnNlEmAKD0xpc3RQbHVnaW5QYWdlcxIlLmJsb2NrbmluamEudjEuTGlzdFBsdWdpblBhZ2VzUmVxdWVzdBomLmJsb2NrbmluamEudjEuTGlzdFBsdWdpblBhZ2VzUmVzcG9uc2USVAoLTGlzdFNTSEtleXMSIS5ibG9ja25pbmphLnYxLkxpc3RTU0hLZXlzUmVxdWVzdBoiLmJsb2NrbmluamEudjEuTGlzdFNTSEtleXNSZXNwb25zZRJXCgxDcmVhdGVTU0hLZXkSIi5ibG9ja25pbmphLnYxLkNyZWF0ZVNTSEtleVJlcXVlc3QaIy5ibG9ja25pbmphLnYxLkNyZWF0ZVNTSEtleVJlc3BvbnNlElcKDFVwZGF0ZVNTSEtleRIiLmJsb2NrbmluamEudjEuVXBkYXRlU1NIS2V5UmVxdWVzdBojLmJsb2NrbmluamEudjEuVXBkYXRlU1NIS2V5UmVzcG9uc2USVwoMRGVsZXRlU1NIS2V5EiIuYmxvY2tuaW5qYS52MS5EZWxldGVTU0hLZXlSZXF1ZXN0GiMuYmxvY2tuaW5qYS52MS5EZWxldGVTU0hLZXlSZXNwb25zZULCAQoRY29tLmJsb2NrbmluamEudjFCDFBsdWdpbnNQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_google_protobuf_timestamp], +); +/** + * PluginStatus represents the current status of a plugin + * + * @generated from enum blockninja.v1.PluginStatus + */ +var PluginStatus; +(function (PluginStatus) { + /** + * @generated from enum value: PLUGIN_STATUS_UNSPECIFIED = 0; + */ + PluginStatus[(PluginStatus["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * @generated from enum value: PLUGIN_STATUS_LOADED = 1; + */ + PluginStatus[(PluginStatus["LOADED"] = 1)] = "LOADED"; + /** + * @generated from enum value: PLUGIN_STATUS_FAILED = 2; + */ + PluginStatus[(PluginStatus["FAILED"] = 2)] = "FAILED"; + /** + * @generated from enum value: PLUGIN_STATUS_DISABLED = 3; + */ + PluginStatus[(PluginStatus["DISABLED"] = 3)] = "DISABLED"; + /** + * @generated from enum value: PLUGIN_STATUS_REBUILDING = 4; + */ + PluginStatus[(PluginStatus["REBUILDING"] = 4)] = "REBUILDING"; + /** + * @generated from enum value: PLUGIN_STATUS_PENDING_RESTART = 5; + */ + PluginStatus[(PluginStatus["PENDING_RESTART"] = 5)] = "PENDING_RESTART"; +})(PluginStatus || (PluginStatus = {})); +/** + * PluginsService provides information about loaded plugins + * + * @generated from service blockninja.v1.PluginsService + */ +const PluginsService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_plugins, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/plugins.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * List all plugins (loaded and failed) + * + * @generated from rpc blockninja.v1.PluginsService.ListPlugins + */ +PluginsService.method.listPlugins; +/** + * Get details about a specific plugin + * + * @generated from rpc blockninja.v1.PluginsService.GetPlugin + */ +PluginsService.method.getPlugin; +/** + * Enable a disabled plugin (requires restart to take effect) + * + * @generated from rpc blockninja.v1.PluginsService.EnablePlugin + */ +PluginsService.method.enablePlugin; +/** + * Disable a loaded plugin (requires restart to take effect) + * + * @generated from rpc blockninja.v1.PluginsService.DisablePlugin + */ +PluginsService.method.disablePlugin; +/** + * Uninstall a git plugin (stages removal for next restart) + * + * @generated from rpc blockninja.v1.PluginsService.UninstallPlugin + */ +PluginsService.method.uninstallPlugin; +/** + * Save per-plugin SSH configuration (key + port for git operations) + * + * @generated from rpc blockninja.v1.PluginsService.SavePluginConfig + */ +PluginsService.method.savePluginConfig; +/** + * Install a plugin from a git repository (stages for next restart) + * + * @generated from rpc blockninja.v1.PluginsService.InstallPlugin + */ +PluginsService.method.installPlugin; +/** + * Update an installed plugin's branch/config (stages for next restart) + * + * @generated from rpc blockninja.v1.PluginsService.UpdatePlugin + */ +PluginsService.method.updatePlugin; +/** + * Get the site-wide deploy key public key + * + * @generated from rpc blockninja.v1.PluginsService.GetDeployKey + */ +PluginsService.method.getDeployKey; +/** + * Generate or regenerate the site-wide deploy key + * + * @generated from rpc blockninja.v1.PluginsService.GenerateDeployKey + */ +PluginsService.method.generateDeployKey; +/** + * Get build log content for a plugin + * + * @generated from rpc blockninja.v1.PluginsService.GetPluginBuildLog + */ +PluginsService.method.getPluginBuildLog; +/** + * List admin pages registered by plugins + * + * @generated from rpc blockninja.v1.PluginsService.ListPluginPages + */ +PluginsService.method.listPluginPages; +/** + * SSH Key Management + * + * @generated from rpc blockninja.v1.PluginsService.ListSSHKeys + */ +PluginsService.method.listSSHKeys; +/** + * @generated from rpc blockninja.v1.PluginsService.CreateSSHKey + */ +PluginsService.method.createSSHKey; +/** + * @generated from rpc blockninja.v1.PluginsService.UpdateSSHKey + */ +PluginsService.method.updateSSHKey; +/** + * @generated from rpc blockninja.v1.PluginsService.DeleteSSHKey + */ +PluginsService.method.deleteSSHKey; + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/posts.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * @generated from rpc blockninja.v1.PostsService.ListPosts + */ +PostsService.method.listPosts; +/** + * @generated from rpc blockninja.v1.PostsService.GetPost + */ +PostsService.method.getPost; +/** + * @generated from rpc blockninja.v1.PostsService.ListPostArchive + */ +PostsService.method.listPostArchive; +/** + * @generated from rpc blockninja.v1.PostsService.GetArchiveCounts + */ +PostsService.method.getArchiveCounts; +/** + * @generated from rpc blockninja.v1.PostsService.GetRelatedPosts + */ +PostsService.method.getRelatedPosts; +/** + * @generated from rpc blockninja.v1.PostsService.GetPostsByAuthor + */ +PostsService.method.getPostsByAuthor; +/** + * @generated from rpc blockninja.v1.PostsService.ListCategories + */ +PostsService.method.listCategories; +/** + * @generated from rpc blockninja.v1.PostsService.GetCategory + */ +PostsService.method.getCategory; +/** + * @generated from rpc blockninja.v1.PostsService.CreateCategory + */ +PostsService.method.createCategory; +/** + * @generated from rpc blockninja.v1.PostsService.UpdateCategory + */ +PostsService.method.updateCategory; +/** + * @generated from rpc blockninja.v1.PostsService.DeleteCategory + */ +PostsService.method.deleteCategory; +/** + * @generated from rpc blockninja.v1.PostsService.GetPostCategories + */ +PostsService.method.getPostCategories; +/** + * @generated from rpc blockninja.v1.PostsService.AddCategoryToPost + */ +PostsService.method.addCategoryToPost; +/** + * @generated from rpc blockninja.v1.PostsService.RemoveCategoryFromPost + */ +PostsService.method.removeCategoryFromPost; +/** + * @generated from rpc blockninja.v1.PostsService.ToggleFeaturedPost + */ +PostsService.method.toggleFeaturedPost; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/redirects.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/redirects.proto. + */ +const file_blockninja_v1_redirects = /*@__PURE__*/ fileDesc( + "Ch1ibG9ja25pbmphL3YxL3JlZGlyZWN0cy5wcm90bxINYmxvY2tuaW5qYS52MSLyAQoIUmVkaXJlY3QSCgoCaWQYASABKAkSEwoLc291cmNlX3BhdGgYAiABKAkSEwoLdGFyZ2V0X3BhdGgYAyABKAkSFQoNcmVkaXJlY3RfdHlwZRgEIAEoBRISCgppc19wYXR0ZXJuGAUgASgIEhEKCWhpdF9jb3VudBgGIAEoAxIuCgpjcmVhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBISCgpjcmVhdGVkX2J5GAkgASgJImwKFUNyZWF0ZVJlZGlyZWN0UmVxdWVzdBITCgtzb3VyY2VfcGF0aBgBIAEoCRITCgt0YXJnZXRfcGF0aBgCIAEoCRIVCg1yZWRpcmVjdF90eXBlGAMgASgFEhIKCmlzX3BhdHRlcm4YBCABKAgiQwoWQ3JlYXRlUmVkaXJlY3RSZXNwb25zZRIpCghyZWRpcmVjdBgBIAEoCzIXLmJsb2NrbmluamEudjEuUmVkaXJlY3QiIAoSR2V0UmVkaXJlY3RSZXF1ZXN0EgoKAmlkGAEgASgJIkAKE0dldFJlZGlyZWN0UmVzcG9uc2USKQoIcmVkaXJlY3QYASABKAsyFy5ibG9ja25pbmphLnYxLlJlZGlyZWN0IjcKFExpc3RSZWRpcmVjdHNSZXF1ZXN0EgwKBHBhZ2UYASABKAUSEQoJcGFnZV9zaXplGAIgASgFIlIKFUxpc3RSZWRpcmVjdHNSZXNwb25zZRIqCglyZWRpcmVjdHMYASADKAsyFy5ibG9ja25pbmphLnYxLlJlZGlyZWN0Eg0KBXRvdGFsGAIgASgDIngKFVVwZGF0ZVJlZGlyZWN0UmVxdWVzdBIKCgJpZBgBIAEoCRITCgtzb3VyY2VfcGF0aBgCIAEoCRITCgt0YXJnZXRfcGF0aBgDIAEoCRIVCg1yZWRpcmVjdF90eXBlGAQgASgFEhIKCmlzX3BhdHRlcm4YBSABKAgiQwoWVXBkYXRlUmVkaXJlY3RSZXNwb25zZRIpCghyZWRpcmVjdBgBIAEoCzIXLmJsb2NrbmluamEudjEuUmVkaXJlY3QiIwoVRGVsZXRlUmVkaXJlY3RSZXF1ZXN0EgoKAmlkGAEgASgJIikKFkRlbGV0ZVJlZGlyZWN0UmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCIiChNUZXN0UmVkaXJlY3RSZXF1ZXN0EgsKA3VybBgBIAEoCSKIAQoUVGVzdFJlZGlyZWN0UmVzcG9uc2USFgoOd291bGRfcmVkaXJlY3QYASABKAgSEgoKdGFyZ2V0X3VybBgCIAEoCRIVCg1yZWRpcmVjdF90eXBlGAMgASgFEi0KDG1hdGNoZWRfcnVsZRgEIAEoCzIXLmJsb2NrbmluamEudjEuUmVkaXJlY3QiGQoXR2V0UmVkaXJlY3RTdGF0c1JlcXVlc3QikAEKGEdldFJlZGlyZWN0U3RhdHNSZXNwb25zZRIXCg90b3RhbF9yZWRpcmVjdHMYASABKAMSEgoKdG90YWxfaGl0cxgCIAEoAxIXCg9wZXJtYW5lbnRfY291bnQYAyABKAMSFwoPdGVtcG9yYXJ5X2NvdW50GAQgASgDEhUKDXBhdHRlcm5fY291bnQYBSABKAMiGwoZQ2xlYXJSZWRpcmVjdENhY2hlUmVxdWVzdCItChpDbGVhclJlZGlyZWN0Q2FjaGVSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIMooGChBSZWRpcmVjdHNTZXJ2aWNlEl0KDkNyZWF0ZVJlZGlyZWN0EiQuYmxvY2tuaW5qYS52MS5DcmVhdGVSZWRpcmVjdFJlcXVlc3QaJS5ibG9ja25pbmphLnYxLkNyZWF0ZVJlZGlyZWN0UmVzcG9uc2USVAoLR2V0UmVkaXJlY3QSIS5ibG9ja25pbmphLnYxLkdldFJlZGlyZWN0UmVxdWVzdBoiLmJsb2NrbmluamEudjEuR2V0UmVkaXJlY3RSZXNwb25zZRJaCg1MaXN0UmVkaXJlY3RzEiMuYmxvY2tuaW5qYS52MS5MaXN0UmVkaXJlY3RzUmVxdWVzdBokLmJsb2NrbmluamEudjEuTGlzdFJlZGlyZWN0c1Jlc3BvbnNlEl0KDlVwZGF0ZVJlZGlyZWN0EiQuYmxvY2tuaW5qYS52MS5VcGRhdGVSZWRpcmVjdFJlcXVlc3QaJS5ibG9ja25pbmphLnYxLlVwZGF0ZVJlZGlyZWN0UmVzcG9uc2USXQoORGVsZXRlUmVkaXJlY3QSJC5ibG9ja25pbmphLnYxLkRlbGV0ZVJlZGlyZWN0UmVxdWVzdBolLmJsb2NrbmluamEudjEuRGVsZXRlUmVkaXJlY3RSZXNwb25zZRJXCgxUZXN0UmVkaXJlY3QSIi5ibG9ja25pbmphLnYxLlRlc3RSZWRpcmVjdFJlcXVlc3QaIy5ibG9ja25pbmphLnYxLlRlc3RSZWRpcmVjdFJlc3BvbnNlEmMKEEdldFJlZGlyZWN0U3RhdHMSJi5ibG9ja25pbmphLnYxLkdldFJlZGlyZWN0U3RhdHNSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5HZXRSZWRpcmVjdFN0YXRzUmVzcG9uc2USaQoSQ2xlYXJSZWRpcmVjdENhY2hlEiguYmxvY2tuaW5qYS52MS5DbGVhclJlZGlyZWN0Q2FjaGVSZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5DbGVhclJlZGlyZWN0Q2FjaGVSZXNwb25zZULEAQoRY29tLmJsb2NrbmluamEudjFCDlJlZGlyZWN0c1Byb3RvUAFaSmdpdC5kZXYuYWxleGR1bm1vdy5jb20vYmxvY2svbmluamEvaW50ZXJuYWwvYXBpL2Jsb2NrbmluamEvdjE7YmxvY2tuaW5qYXYxogIDQlhYqgINQmxvY2tuaW5qYS5WMcoCDUJsb2NrbmluamFcVjHiAhlCbG9ja25pbmphXFYxXEdQQk1ldGFkYXRh6gIOQmxvY2tuaW5qYTo6VjFiBnByb3RvMw", + [file_google_protobuf_timestamp], +); +/** + * RedirectsService manages URL redirects + * + * @generated from service blockninja.v1.RedirectsService + */ +const RedirectsService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_redirects, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/redirects.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Create a new redirect + * + * @generated from rpc blockninja.v1.RedirectsService.CreateRedirect + */ +RedirectsService.method.createRedirect; +/** + * Get a single redirect by ID + * + * @generated from rpc blockninja.v1.RedirectsService.GetRedirect + */ +RedirectsService.method.getRedirect; +/** + * List all redirects with pagination + * + * @generated from rpc blockninja.v1.RedirectsService.ListRedirects + */ +RedirectsService.method.listRedirects; +/** + * Update an existing redirect + * + * @generated from rpc blockninja.v1.RedirectsService.UpdateRedirect + */ +RedirectsService.method.updateRedirect; +/** + * Delete a redirect + * + * @generated from rpc blockninja.v1.RedirectsService.DeleteRedirect + */ +RedirectsService.method.deleteRedirect; +/** + * Test a redirect (check what URL would redirect to) + * + * @generated from rpc blockninja.v1.RedirectsService.TestRedirect + */ +RedirectsService.method.testRedirect; +/** + * Get redirect statistics + * + * @generated from rpc blockninja.v1.RedirectsService.GetRedirectStats + */ +RedirectsService.method.getRedirectStats; +/** + * Clear redirect cache + * + * @generated from rpc blockninja.v1.RedirectsService.ClearRedirectCache + */ +RedirectsService.method.clearRedirectCache; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/seo.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/seo.proto. + */ +const file_blockninja_v1_seo = /*@__PURE__*/ fileDesc( + "ChdibG9ja25pbmphL3YxL3Nlby5wcm90bxINYmxvY2tuaW5qYS52MSIXChVHZXRTRU9PdmVydmlld1JlcXVlc3QiRgoWR2V0U0VPT3ZlcnZpZXdSZXNwb25zZRIsCghvdmVydmlldxgBIAEoCzIaLmJsb2NrbmluamEudjEuU0VPT3ZlcnZpZXcirwIKC1NFT092ZXJ2aWV3EhUKDW92ZXJhbGxfc2NvcmUYASABKAUSEwoLdG90YWxfcGFnZXMYAiABKAUSFwoPdG90YWxfcHVibGlzaGVkGAMgASgFEiAKGHBhZ2VzX21pc3NpbmdfbWV0YV90aXRsZRgEIAEoBRImCh5wYWdlc19taXNzaW5nX21ldGFfZGVzY3JpcHRpb24YBSABKAUSHgoWcGFnZXNfbWlzc2luZ19vZ19pbWFnZRgGIAEoBRIlCh1wYWdlc19taXNzaW5nX2ZvY3VzX2tleXBocmFzZRgHIAEoBRIlCh1wYWdlc193aXRoX3Bvb3Jfa2V5d29yZF9zY29yZRgIIAEoBRIjChtwYWdlc193aXRoX3Bvb3JfcmVhZGFiaWxpdHkYCSABKAUiggIKE0xpc3RTRU9BdWRpdFJlcXVlc3QSDAoEcGFnZRgBIAEoBRIRCglwYWdlX3NpemUYAiABKAUSFgoJcG9zdF90eXBlGAMgASgJSACIAQESEwoGc3RhdHVzGAQgASgJSAGIAQESEgoFaXNzdWUYBSABKAlIAogBARITCgZzZWFyY2gYBiABKAlIA4gBARIUCgdzb3J0X2J5GAcgASgJSASIAQESFgoJc29ydF9kZXNjGAggASgISAWIAQFCDAoKX3Bvc3RfdHlwZUIJCgdfc3RhdHVzQggKBl9pc3N1ZUIJCgdfc2VhcmNoQgoKCF9zb3J0X2J5QgwKCl9zb3J0X2Rlc2MicgoUTGlzdFNFT0F1ZGl0UmVzcG9uc2USKgoFaXRlbXMYASADKAsyGy5ibG9ja25pbmphLnYxLlNFT0F1ZGl0SXRlbRINCgV0b3RhbBgCIAEoBRIMCgRwYWdlGAMgASgFEhEKCXBhZ2Vfc2l6ZRgEIAEoBSKfBwoMU0VPQXVkaXRJdGVtEgoKAmlkGAEgASgJEg0KBXRpdGxlGAIgASgJEgwKBHNsdWcYAyABKAkSKgoJcG9zdF90eXBlGAQgASgOMhcuYmxvY2tuaW5qYS52MS5Qb3N0VHlwZRIpCgZzdGF0dXMYBSABKA4yGS5ibG9ja25pbmphLnYxLlBhZ2VTdGF0dXMSFgoOaGFzX21ldGFfdGl0bGUYBiABKAgSHAoUaGFzX21ldGFfZGVzY3JpcHRpb24YByABKAgSFAoMaGFzX29nX2ltYWdlGAggASgIEhsKE2hhc19mb2N1c19rZXlwaHJhc2UYCSABKAgSGQoRbWV0YV90aXRsZV9zdGF0dXMYFyABKAkSHwoXbWV0YV9kZXNjcmlwdGlvbl9zdGF0dXMYGCABKAkSFwoPb2dfaW1hZ2Vfc3RhdHVzGBkgASgJEhcKCm1ldGFfdGl0bGUYCiABKAlIAIgBARIdChBtZXRhX2Rlc2NyaXB0aW9uGAsgASgJSAGIAQESHAoPZm9jdXNfa2V5cGhyYXNlGAwgASgJSAKIAQESGgoNa2V5d29yZF9zY29yZRgNIAEoBUgDiAEBEh4KEXJlYWRhYmlsaXR5X3Njb3JlGA4gASgFSASIAQESGAoLYW5hbHl6ZWRfYXQYDyABKAlIBYgBARIVCghvZ190aXRsZRgQIAEoCUgGiAEBEhsKDm9nX2Rlc2NyaXB0aW9uGBEgASgJSAeIAQESFQoIb2dfaW1hZ2UYEiABKAlICIgBARIaCg1jYW5vbmljYWxfdXJsGBMgASgJSAmIAQESHQoQcm9ib3RzX2RpcmVjdGl2ZRgUIAEoCUgKiAEBEhcKCndvcmRfY291bnQYFSABKAVIC4gBARIzCg5rZXl3b3JkX2NoZWNrcxgWIAMoCzIbLmJsb2NrbmluamEudjEuU0VPQ2hlY2tJdGVtQg0KC19tZXRhX3RpdGxlQhMKEV9tZXRhX2Rlc2NyaXB0aW9uQhIKEF9mb2N1c19rZXlwaHJhc2VCEAoOX2tleXdvcmRfc2NvcmVCFAoSX3JlYWRhYmlsaXR5X3Njb3JlQg4KDF9hbmFseXplZF9hdEILCglfb2dfdGl0bGVCEQoPX29nX2Rlc2NyaXB0aW9uQgsKCV9vZ19pbWFnZUIQCg5fY2Fub25pY2FsX3VybEITChFfcm9ib3RzX2RpcmVjdGl2ZUINCgtfd29yZF9jb3VudCIoChVBbmFseXplUGFnZVNFT1JlcXVlc3QSDwoHcGFnZV9pZBgBIAEoCSJKChZBbmFseXplUGFnZVNFT1Jlc3BvbnNlEjAKBnJlc3VsdBgBIAEoCzIgLmJsb2NrbmluamEudjEuU0VPQW5hbHlzaXNSZXN1bHQitQEKEVNFT0FuYWx5c2lzUmVzdWx0Eg8KB3BhZ2VfaWQYASABKAkSFQoNa2V5d29yZF9zY29yZRgCIAEoBRIZChFyZWFkYWJpbGl0eV9zY29yZRgDIAEoBRIzCg5rZXl3b3JkX2NoZWNrcxgEIAMoCzIbLmJsb2NrbmluamEudjEuU0VPQ2hlY2tJdGVtEhIKCndvcmRfY291bnQYBSABKAUSFAoMZmxlc2NoX3Njb3JlGAYgASgBIj4KDFNFT0NoZWNrSXRlbRINCgVjaGVjaxgBIAEoCRIOCgZzdGF0dXMYAiABKAkSDwoHbWVzc2FnZRgDIAEoCSJbChpCdWxrR2VuZXJhdGVTRU9NZXRhUmVxdWVzdBIQCghwYWdlX2lkcxgBIAMoCRIrCgZmaWVsZHMYAiADKA4yGy5ibG9ja25pbmphLnYxLlNFT0ZpZWxkVHlwZSJ4ChtCdWxrR2VuZXJhdGVTRU9NZXRhUmVzcG9uc2USLQoHcmVzdWx0cxgBIAMoCzIcLmJsb2NrbmluamEudjEuQnVsa1NFT1Jlc3VsdBIVCg1zdWNjZXNzX2NvdW50GAIgASgFEhMKC2Vycm9yX2NvdW50GAMgASgFIv8BCg1CdWxrU0VPUmVzdWx0Eg8KB3BhZ2VfaWQYASABKAkSDwoHc3VjY2VzcxgCIAEoCBISCgVlcnJvchgDIAEoCUgAiAEBEhcKCm1ldGFfdGl0bGUYBCABKAlIAYgBARIdChBtZXRhX2Rlc2NyaXB0aW9uGAUgASgJSAKIAQESFQoIb2dfdGl0bGUYBiABKAlIA4gBARIbCg5vZ19kZXNjcmlwdGlvbhgHIAEoCUgEiAEBQggKBl9lcnJvckINCgtfbWV0YV90aXRsZUITChFfbWV0YV9kZXNjcmlwdGlvbkILCglfb2dfdGl0bGVCEQoPX29nX2Rlc2NyaXB0aW9uIo8DChRVcGRhdGVQYWdlU0VPUmVxdWVzdBIPCgdwYWdlX2lkGAEgASgJEhcKCm1ldGFfdGl0bGUYAiABKAlIAIgBARIdChBtZXRhX2Rlc2NyaXB0aW9uGAMgASgJSAGIAQESFQoIb2dfdGl0bGUYBCABKAlIAogBARIbCg5vZ19kZXNjcmlwdGlvbhgFIAEoCUgDiAEBEhUKCG9nX2ltYWdlGAYgASgJSASIAQESHAoPZm9jdXNfa2V5cGhyYXNlGAcgASgJSAWIAQESGgoNY2Fub25pY2FsX3VybBgIIAEoCUgGiAEBEh0KEHJvYm90c19kaXJlY3RpdmUYCSABKAlIB4gBAUINCgtfbWV0YV90aXRsZUITChFfbWV0YV9kZXNjcmlwdGlvbkILCglfb2dfdGl0bGVCEQoPX29nX2Rlc2NyaXB0aW9uQgsKCV9vZ19pbWFnZUISChBfZm9jdXNfa2V5cGhyYXNlQhAKDl9jYW5vbmljYWxfdXJsQhMKEV9yb2JvdHNfZGlyZWN0aXZlIkIKFVVwZGF0ZVBhZ2VTRU9SZXNwb25zZRIpCgRpdGVtGAEgASgLMhsuYmxvY2tuaW5qYS52MS5TRU9BdWRpdEl0ZW0y7QMKClNFT1NlcnZpY2USXQoOR2V0U0VPT3ZlcnZpZXcSJC5ibG9ja25pbmphLnYxLkdldFNFT092ZXJ2aWV3UmVxdWVzdBolLmJsb2NrbmluamEudjEuR2V0U0VPT3ZlcnZpZXdSZXNwb25zZRJXCgxMaXN0U0VPQXVkaXQSIi5ibG9ja25pbmphLnYxLkxpc3RTRU9BdWRpdFJlcXVlc3QaIy5ibG9ja25pbmphLnYxLkxpc3RTRU9BdWRpdFJlc3BvbnNlEl0KDkFuYWx5emVQYWdlU0VPEiQuYmxvY2tuaW5qYS52MS5BbmFseXplUGFnZVNFT1JlcXVlc3QaJS5ibG9ja25pbmphLnYxLkFuYWx5emVQYWdlU0VPUmVzcG9uc2USbAoTQnVsa0dlbmVyYXRlU0VPTWV0YRIpLmJsb2NrbmluamEudjEuQnVsa0dlbmVyYXRlU0VPTWV0YVJlcXVlc3QaKi5ibG9ja25pbmphLnYxLkJ1bGtHZW5lcmF0ZVNFT01ldGFSZXNwb25zZRJaCg1VcGRhdGVQYWdlU0VPEiMuYmxvY2tuaW5qYS52MS5VcGRhdGVQYWdlU0VPUmVxdWVzdBokLmJsb2NrbmluamEudjEuVXBkYXRlUGFnZVNFT1Jlc3BvbnNlQr4BChFjb20uYmxvY2tuaW5qYS52MUIIU2VvUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_blockninja_v1_pages, file_blockninja_v1_ai], +); +/** + * SEOService provides SEO health monitoring and auditing + * + * @generated from service blockninja.v1.SEOService + */ +const SEOService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_seo, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/seo.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Get overall SEO health statistics + * + * @generated from rpc blockninja.v1.SEOService.GetSEOOverview + */ +SEOService.method.getSEOOverview; +/** + * List pages/posts with SEO audit data + * + * @generated from rpc blockninja.v1.SEOService.ListSEOAudit + */ +SEOService.method.listSEOAudit; +/** + * Analyze SEO for a specific page (on-demand) + * + * @generated from rpc blockninja.v1.SEOService.AnalyzePageSEO + */ +SEOService.method.analyzePageSEO; +/** + * Bulk generate SEO meta for multiple pages (max 10) + * + * @generated from rpc blockninja.v1.SEOService.BulkGenerateSEOMeta + */ +SEOService.method.bulkGenerateSEOMeta; +/** + * Update SEO fields for a page (from audit dashboard quick edit) + * + * @generated from rpc blockninja.v1.SEOService.UpdatePageSEO + */ +SEOService.method.updatePageSEO; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/series.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/series.proto. + */ +const file_blockninja_v1_series = /*@__PURE__*/ fileDesc( + "ChpibG9ja25pbmphL3YxL3Nlcmllcy5wcm90bxINYmxvY2tuaW5qYS52MSLqAQoGU2VyaWVzEgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSDAoEc2x1ZxgDIAEoCRITCgtkZXNjcmlwdGlvbhgEIAEoCRIWCg5jb3Zlcl9pbWFnZV9pZBgFIAEoCRIXCg9jb3Zlcl9pbWFnZV91cmwYBiABKAkSLgoKY3JlYXRlZF9hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKdXBkYXRlZF9hdBgIIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASEgoKcG9zdF9jb3VudBgJIAEoBSLUAQoKU2VyaWVzUG9zdBIKCgJpZBgBIAEoCRINCgV0aXRsZRgCIAEoCRIMCgRzbHVnGAMgASgJEg8KB2V4Y2VycHQYBCABKAkSGgoSZmVhdHVyZWRfaW1hZ2VfdXJsGAUgASgJEjAKDHB1Ymxpc2hlZF9hdBgGIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASHAoUcmVhZGluZ190aW1lX21pbnV0ZXMYByABKAUSEAoIcG9zaXRpb24YCCABKAUSDgoGc3RhdHVzGAkgASgJIi8KEUxpc3RTZXJpZXNSZXF1ZXN0EhoKEmluY2x1ZGVfcG9zdF9jb3VudBgBIAEoCCI7ChJMaXN0U2VyaWVzUmVzcG9uc2USJQoGc2VyaWVzGAEgAygLMhUuYmxvY2tuaW5qYS52MS5TZXJpZXMiHgoQR2V0U2VyaWVzUmVxdWVzdBIKCgJpZBgBIAEoCSI6ChFHZXRTZXJpZXNSZXNwb25zZRIlCgZzZXJpZXMYASABKAsyFS5ibG9ja25pbmphLnYxLlNlcmllcyImChZHZXRTZXJpZXNCeVNsdWdSZXF1ZXN0EgwKBHNsdWcYASABKAkiQAoXR2V0U2VyaWVzQnlTbHVnUmVzcG9uc2USJQoGc2VyaWVzGAEgASgLMhUuYmxvY2tuaW5qYS52MS5TZXJpZXMiXgoTQ3JlYXRlU2VyaWVzUmVxdWVzdBIMCgRuYW1lGAEgASgJEgwKBHNsdWcYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSFgoOY292ZXJfaW1hZ2VfaWQYBCABKAkiPQoUQ3JlYXRlU2VyaWVzUmVzcG9uc2USJQoGc2VyaWVzGAEgASgLMhUuYmxvY2tuaW5qYS52MS5TZXJpZXMiagoTVXBkYXRlU2VyaWVzUmVxdWVzdBIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEgwKBHNsdWcYAyABKAkSEwoLZGVzY3JpcHRpb24YBCABKAkSFgoOY292ZXJfaW1hZ2VfaWQYBSABKAkiPQoUVXBkYXRlU2VyaWVzUmVzcG9uc2USJQoGc2VyaWVzGAEgASgLMhUuYmxvY2tuaW5qYS52MS5TZXJpZXMiIQoTRGVsZXRlU2VyaWVzUmVxdWVzdBIKCgJpZBgBIAEoCSInChREZWxldGVTZXJpZXNSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIIkIKFUdldFNlcmllc1Bvc3RzUmVxdWVzdBIRCglzZXJpZXNfaWQYASABKAkSFgoOaW5jbHVkZV9kcmFmdHMYAiABKAgiaQoWR2V0U2VyaWVzUG9zdHNSZXNwb25zZRIoCgVwb3N0cxgBIAMoCzIZLmJsb2NrbmluamEudjEuU2VyaWVzUG9zdBIlCgZzZXJpZXMYAiABKAsyFS5ibG9ja25pbmphLnYxLlNlcmllcyJOChZBZGRQb3N0VG9TZXJpZXNSZXF1ZXN0EhEKCXNlcmllc19pZBgBIAEoCRIPCgdwYWdlX2lkGAIgASgJEhAKCHBvc2l0aW9uGAMgASgFIjwKF0FkZFBvc3RUb1Nlcmllc1Jlc3BvbnNlEg8KB3N1Y2Nlc3MYASABKAgSEAoIcG9zaXRpb24YAiABKAUiQQobUmVtb3ZlUG9zdEZyb21TZXJpZXNSZXF1ZXN0EhEKCXNlcmllc19pZBgBIAEoCRIPCgdwYWdlX2lkGAIgASgJIi8KHFJlbW92ZVBvc3RGcm9tU2VyaWVzUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCJAChlSZW9yZGVyU2VyaWVzUG9zdHNSZXF1ZXN0EhEKCXNlcmllc19pZBgBIAEoCRIQCghwYWdlX2lkcxgCIAMoCSItChpSZW9yZGVyU2VyaWVzUG9zdHNSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIIi0KGkdldFNlcmllc05hdmlnYXRpb25SZXF1ZXN0Eg8KB3BhZ2VfaWQYASABKAki9QEKG0dldFNlcmllc05hdmlnYXRpb25SZXNwb25zZRIlCgZzZXJpZXMYASABKAsyFS5ibG9ja25pbmphLnYxLlNlcmllcxIqCgdjdXJyZW50GAIgASgLMhkuYmxvY2tuaW5qYS52MS5TZXJpZXNQb3N0EisKCHByZXZpb3VzGAMgASgLMhkuYmxvY2tuaW5qYS52MS5TZXJpZXNQb3N0EicKBG5leHQYBCABKAsyGS5ibG9ja25pbmphLnYxLlNlcmllc1Bvc3QSEwoLdG90YWxfcG9zdHMYBSABKAUSGAoQY3VycmVudF9wb3NpdGlvbhgGIAEoBTKqCAoNU2VyaWVzU2VydmljZRJRCgpMaXN0U2VyaWVzEiAuYmxvY2tuaW5qYS52MS5MaXN0U2VyaWVzUmVxdWVzdBohLmJsb2NrbmluamEudjEuTGlzdFNlcmllc1Jlc3BvbnNlEk4KCUdldFNlcmllcxIfLmJsb2NrbmluamEudjEuR2V0U2VyaWVzUmVxdWVzdBogLmJsb2NrbmluamEudjEuR2V0U2VyaWVzUmVzcG9uc2USYAoPR2V0U2VyaWVzQnlTbHVnEiUuYmxvY2tuaW5qYS52MS5HZXRTZXJpZXNCeVNsdWdSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5HZXRTZXJpZXNCeVNsdWdSZXNwb25zZRJXCgxDcmVhdGVTZXJpZXMSIi5ibG9ja25pbmphLnYxLkNyZWF0ZVNlcmllc1JlcXVlc3QaIy5ibG9ja25pbmphLnYxLkNyZWF0ZVNlcmllc1Jlc3BvbnNlElcKDFVwZGF0ZVNlcmllcxIiLmJsb2NrbmluamEudjEuVXBkYXRlU2VyaWVzUmVxdWVzdBojLmJsb2NrbmluamEudjEuVXBkYXRlU2VyaWVzUmVzcG9uc2USVwoMRGVsZXRlU2VyaWVzEiIuYmxvY2tuaW5qYS52MS5EZWxldGVTZXJpZXNSZXF1ZXN0GiMuYmxvY2tuaW5qYS52MS5EZWxldGVTZXJpZXNSZXNwb25zZRJdCg5HZXRTZXJpZXNQb3N0cxIkLmJsb2NrbmluamEudjEuR2V0U2VyaWVzUG9zdHNSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5HZXRTZXJpZXNQb3N0c1Jlc3BvbnNlEmAKD0FkZFBvc3RUb1NlcmllcxIlLmJsb2NrbmluamEudjEuQWRkUG9zdFRvU2VyaWVzUmVxdWVzdBomLmJsb2NrbmluamEudjEuQWRkUG9zdFRvU2VyaWVzUmVzcG9uc2USbwoUUmVtb3ZlUG9zdEZyb21TZXJpZXMSKi5ibG9ja25pbmphLnYxLlJlbW92ZVBvc3RGcm9tU2VyaWVzUmVxdWVzdBorLmJsb2NrbmluamEudjEuUmVtb3ZlUG9zdEZyb21TZXJpZXNSZXNwb25zZRJpChJSZW9yZGVyU2VyaWVzUG9zdHMSKC5ibG9ja25pbmphLnYxLlJlb3JkZXJTZXJpZXNQb3N0c1JlcXVlc3QaKS5ibG9ja25pbmphLnYxLlJlb3JkZXJTZXJpZXNQb3N0c1Jlc3BvbnNlEmwKE0dldFNlcmllc05hdmlnYXRpb24SKS5ibG9ja25pbmphLnYxLkdldFNlcmllc05hdmlnYXRpb25SZXF1ZXN0GiouYmxvY2tuaW5qYS52MS5HZXRTZXJpZXNOYXZpZ2F0aW9uUmVzcG9uc2VCwQEKEWNvbS5ibG9ja25pbmphLnYxQgtTZXJpZXNQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_google_protobuf_timestamp], +); +/** + * SeriesService manages post series for multi-part content + * + * @generated from service blockninja.v1.SeriesService + */ +const SeriesService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_series, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/series.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * List all series + * + * @generated from rpc blockninja.v1.SeriesService.ListSeries + */ +SeriesService.method.listSeries; +/** + * Get a single series by ID + * + * @generated from rpc blockninja.v1.SeriesService.GetSeries + */ +SeriesService.method.getSeries; +/** + * Get series by slug (for public series pages) + * + * @generated from rpc blockninja.v1.SeriesService.GetSeriesBySlug + */ +SeriesService.method.getSeriesBySlug; +/** + * Create a new series + * + * @generated from rpc blockninja.v1.SeriesService.CreateSeries + */ +SeriesService.method.createSeries; +/** + * Update a series + * + * @generated from rpc blockninja.v1.SeriesService.UpdateSeries + */ +SeriesService.method.updateSeries; +/** + * Delete a series + * + * @generated from rpc blockninja.v1.SeriesService.DeleteSeries + */ +SeriesService.method.deleteSeries; +/** + * Get posts in a series (ordered by position) + * + * @generated from rpc blockninja.v1.SeriesService.GetSeriesPosts + */ +SeriesService.method.getSeriesPosts; +/** + * Add a post to a series + * + * @generated from rpc blockninja.v1.SeriesService.AddPostToSeries + */ +SeriesService.method.addPostToSeries; +/** + * Remove a post from a series + * + * @generated from rpc blockninja.v1.SeriesService.RemovePostFromSeries + */ +SeriesService.method.removePostFromSeries; +/** + * Reorder posts in a series + * + * @generated from rpc blockninja.v1.SeriesService.ReorderSeriesPosts + */ +SeriesService.method.reorderSeriesPosts; +/** + * Get series navigation for a post (prev/next in series) + * + * @generated from rpc blockninja.v1.SeriesService.GetSeriesNavigation + */ +SeriesService.method.getSeriesNavigation; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/settings.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/settings.proto. + */ +const file_blockninja_v1_settings = /*@__PURE__*/ fileDesc( + "ChxibG9ja25pbmphL3YxL3NldHRpbmdzLnByb3RvEg1ibG9ja25pbmphLnYxIpYBCgdTZXR0aW5nEgsKA2tleRgBIAEoCRImCgV2YWx1ZRgCIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSLgoKdXBkYXRlZF9hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFwoKdXBkYXRlZF9ieRgEIAEoCUgAiAEBQg0KC191cGRhdGVkX2J5Iv8BCgtTb2NpYWxMaW5rcxIUCgd0d2l0dGVyGAEgASgJSACIAQESFQoIZmFjZWJvb2sYAiABKAlIAYgBARIVCghsaW5rZWRpbhgDIAEoCUgCiAEBEhYKCWluc3RhZ3JhbRgEIAEoCUgDiAEBEhQKB3lvdXR1YmUYBSABKAlIBIgBARITCgZnaXRodWIYBiABKAlIBYgBARITCgZ0aWt0b2sYByABKAlIBogBAUIKCghfdHdpdHRlckILCglfZmFjZWJvb2tCCwoJX2xpbmtlZGluQgwKCl9pbnN0YWdyYW1CCgoIX3lvdXR1YmVCCQoHX2dpdGh1YkIJCgdfdGlrdG9rIqABCgpMZWdhbExpbmtzEh8KEnByaXZhY3lfcG9saWN5X3VybBgBIAEoCUgAiAEBEhYKCXRlcm1zX3VybBgCIAEoCUgBiAEBEh4KEWNvb2tpZV9wb2xpY3lfdXJsGAMgASgJSAKIAQFCFQoTX3ByaXZhY3lfcG9saWN5X3VybEIMCgpfdGVybXNfdXJsQhQKEl9jb29raWVfcG9saWN5X3VybCJrCgtDb250YWN0SW5mbxISCgVlbWFpbBgBIAEoCUgAiAEBEhIKBXBob25lGAIgASgJSAGIAQESFAoHYWRkcmVzcxgDIAEoCUgCiAEBQggKBl9lbWFpbEIICgZfcGhvbmVCCgoIX2FkZHJlc3MiqwIKC1Nlb0RlZmF1bHRzEh0KEG1ldGFfZGVzY3JpcHRpb24YASABKAlIAIgBARIdChBkZWZhdWx0X29nX2ltYWdlGAIgASgJSAGIAQESGwoOdHdpdHRlcl9oYW5kbGUYAyABKAlIAogBARIZCgxvZ19zaXRlX25hbWUYBCABKAlIA4gBARIXCgpyb2JvdHNfdHh0GAUgASgJSASIAQESHAoPc2l0ZW1hcF9lbmFibGVkGAYgASgISAWIAQFCEwoRX21ldGFfZGVzY3JpcHRpb25CEwoRX2RlZmF1bHRfb2dfaW1hZ2VCEQoPX3R3aXR0ZXJfaGFuZGxlQg8KDV9vZ19zaXRlX25hbWVCDQoLX3JvYm90c190eHRCEgoQX3NpdGVtYXBfZW5hYmxlZCK/AQoPQW5hbHl0aWNzQ29uZmlnEiAKE2dvb2dsZV9hbmFseXRpY3NfaWQYASABKAlIAIgBARIgChNjdXN0b21faGVhZF9zY3JpcHRzGAIgASgJSAGIAQESIAoTY3VzdG9tX2JvZHlfc2NyaXB0cxgDIAEoCUgCiAEBQhYKFF9nb29nbGVfYW5hbHl0aWNzX2lkQhYKFF9jdXN0b21faGVhZF9zY3JpcHRzQhYKFF9jdXN0b21fYm9keV9zY3JpcHRzItADChZBaU9wdGltaXphdGlvbkRlZmF1bHRzEhUKCGxsbXNfdHh0GAEgASgJSACIAQESHQoQbGxtc190eHRfZW5hYmxlZBgCIAEoCEgBiAEBEhsKDmdwdGJvdF9hbGxvd2VkGAMgASgISAKIAQESIgoVcGVycGxleGl0eWJvdF9hbGxvd2VkGAQgASgISAOIAQESHgoRY2xhdWRlYm90X2FsbG93ZWQYBSABKAhIBIgBARIkChdnb29nbGVfZXh0ZW5kZWRfYWxsb3dlZBgGIAEoCEgFiAEBEiUKGG9yZ2FuaXphdGlvbl9kZXNjcmlwdGlvbhgHIAEoCUgGiAEBEh4KEXNpdGVfZmFxX2Jsb2NrX2lkGAggASgJSAeIAQFCCwoJX2xsbXNfdHh0QhMKEV9sbG1zX3R4dF9lbmFibGVkQhEKD19ncHRib3RfYWxsb3dlZEIYChZfcGVycGxleGl0eWJvdF9hbGxvd2VkQhQKEl9jbGF1ZGVib3RfYWxsb3dlZEIaChhfZ29vZ2xlX2V4dGVuZGVkX2FsbG93ZWRCGwoZX29yZ2FuaXphdGlvbl9kZXNjcmlwdGlvbkIUChJfc2l0ZV9mYXFfYmxvY2tfaWQifwoPVHVybnN0aWxlQ29uZmlnEhQKB2VuYWJsZWQYASABKAhIAIgBARIVCghzaXRlX2tleRgCIAEoCUgBiAEBEhcKCnNlY3JldF9rZXkYAyABKAlIAogBAUIKCghfZW5hYmxlZEILCglfc2l0ZV9rZXlCDQoLX3NlY3JldF9rZXkikgEKEkxvY2FsaXphdGlvbkNvbmZpZxIVCgh0aW1lem9uZRgBIAEoCUgAiAEBEhgKC2RhdGVfZm9ybWF0GAIgASgJSAGIAQESGwoOZGVmYXVsdF9sb2NhbGUYAyABKAlIAogBAUILCglfdGltZXpvbmVCDgoMX2RhdGVfZm9ybWF0QhEKD19kZWZhdWx0X2xvY2FsZSLiAQoOQWR2YW5jZWRDb25maWcSHQoQbWFpbnRlbmFuY2VfbW9kZRgBIAEoCEgAiAEBEiAKE21haW50ZW5hbmNlX21lc3NhZ2UYAiABKAlIAYgBARIfChJjdXN0b21fNDA0X3BhZ2VfaWQYAyABKAlIAogBARIqCglzaXRlX21vZGUYBCABKA4yFy5ibG9ja25pbmphLnYxLlNpdGVNb2RlQhMKEV9tYWludGVuYW5jZV9tb2RlQhYKFF9tYWludGVuYW5jZV9tZXNzYWdlQhUKE19jdXN0b21fNDA0X3BhZ2VfaWQiQwoHV2ViaG9vaxIKCgJpZBgBIAEoCRILCgN1cmwYAiABKAkSDgoGZXZlbnRzGAMgAygJEg8KB2VuYWJsZWQYBCABKAgi0wsKDFNpdGVTZXR0aW5ncxINCgV0aXRsZRgBIAEoCRITCgtkZXNjcmlwdGlvbhgCIAEoCRIYChBkZWZhdWx0X3RlbXBsYXRlGAMgASgJEhMKBmRvbWFpbhgQIAEoCUgAiAEBEhQKB2Zhdmljb24YBCABKAlIAYgBARIRCgRsb2dvGAUgASgJSAKIAQESFQoIbG9nb19hbHQYBiABKAlIA4gBARIdChBhcHBsZV90b3VjaF9pY29uGAcgASgJSASIAQESMAoMc29jaWFsX2xpbmtzGAggASgLMhouYmxvY2tuaW5qYS52MS5Tb2NpYWxMaW5rcxIuCgtsZWdhbF9saW5rcxgJIAEoCzIZLmJsb2NrbmluamEudjEuTGVnYWxMaW5rcxIwCgxjb250YWN0X2luZm8YCiABKAsyGi5ibG9ja25pbmphLnYxLkNvbnRhY3RJbmZvEjAKDHNlb19kZWZhdWx0cxgLIAEoCzIaLmJsb2NrbmluamEudjEuU2VvRGVmYXVsdHMSMQoJYW5hbHl0aWNzGAwgASgLMh4uYmxvY2tuaW5qYS52MS5BbmFseXRpY3NDb25maWcSNwoMbG9jYWxpemF0aW9uGA0gASgLMiEuYmxvY2tuaW5qYS52MS5Mb2NhbGl6YXRpb25Db25maWcSLwoIYWR2YW5jZWQYDiABKAsyHS5ibG9ja25pbmphLnYxLkFkdmFuY2VkQ29uZmlnEigKCHdlYmhvb2tzGA8gAygLMhYuYmxvY2tuaW5qYS52MS5XZWJob29rEhsKE2VuYWJsZV9hdXRob3JfcGFnZXMYESABKAgSEAoIZGV2X21vZGUYHSABKAgSGAoLYmxvZ19wcmVmaXgYEiABKAlIBYgBARI+Cg9haV9vcHRpbWl6YXRpb24YEyABKAsyJS5ibG9ja25pbmphLnYxLkFpT3B0aW1pemF0aW9uRGVmYXVsdHMSMQoJdHVybnN0aWxlGBQgASgLMh4uYmxvY2tuaW5qYS52MS5UdXJuc3RpbGVDb25maWcSIAoTZ29vZ2xlX21hcHNfYXBpX2tleRgVIAEoCUgGiAEBEh4KEXBsdWdpbl9kZXBsb3lfa2V5GBcgASgJSAeIAQESIwoWZ29vZ2xlX29hdXRoX2NsaWVudF9pZBgYIAEoCUgIiAEBEicKGmdvb2dsZV9vYXV0aF9jbGllbnRfc2VjcmV0GBkgASgJSAmIAQESHAoPZmFjZWJvb2tfYXBwX2lkGBogASgJSAqIAQESIAoTZmFjZWJvb2tfYXBwX3NlY3JldBgbIAEoCUgLiAEBEjYKD21vZGVyYXRpb25fbW9kZRgcIAEoDjIdLmJsb2NrbmluamEudjEuTW9kZXJhdGlvbk1vZGUSGwoOdmlzaW9uX2FwaV9rZXkYHiABKAlIDIgBARIdChBwaG90b2RuYV9hcGlfa2V5GB8gASgJSA2IAQESIQoUY3NhbV9yZXBvcnRpbmdfZW1haWwYICABKAlIDogBARIhChRxdWFyYW50aW5lX3RocmVzaG9sZBghIAEoCUgPiAEBQgkKB19kb21haW5CCgoIX2Zhdmljb25CBwoFX2xvZ29CCwoJX2xvZ29fYWx0QhMKEV9hcHBsZV90b3VjaF9pY29uQg4KDF9ibG9nX3ByZWZpeEIWChRfZ29vZ2xlX21hcHNfYXBpX2tleUIUChJfcGx1Z2luX2RlcGxveV9rZXlCGQoXX2dvb2dsZV9vYXV0aF9jbGllbnRfaWRCHQobX2dvb2dsZV9vYXV0aF9jbGllbnRfc2VjcmV0QhIKEF9mYWNlYm9va19hcHBfaWRCFgoUX2ZhY2Vib29rX2FwcF9zZWNyZXRCEQoPX3Zpc2lvbl9hcGlfa2V5QhMKEV9waG90b2RuYV9hcGlfa2V5QhcKFV9jc2FtX3JlcG9ydGluZ19lbWFpbEIXChVfcXVhcmFudGluZV90aHJlc2hvbGQifQoMVGVtcGxhdGVJbmZvEgsKA2tleRgBIAEoCRIMCgRuYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEhIKCmlzX2VuYWJsZWQYBCABKAgSEwoLYmxvY2tfdHlwZXMYBSADKAkSFAoMaGFzX3NldHRpbmdzGAYgASgIIlIKEFBhZ2VUZW1wbGF0ZUluZm8SCwoDa2V5GAEgASgJEg0KBXRpdGxlGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEg0KBXNsb3RzGAQgAygJImQKEUdldFNldHRpbmdSZXF1ZXN0Ei0KCGtleV9lbnVtGAEgASgOMhkuYmxvY2tuaW5qYS52MS5TZXR0aW5nS2V5SAASFAoKa2V5X3N0cmluZxgCIAEoCUgAQgoKCGtleV90eXBlIj0KEkdldFNldHRpbmdSZXNwb25zZRInCgdzZXR0aW5nGAEgASgLMhYuYmxvY2tuaW5qYS52MS5TZXR0aW5nIowBChFTZXRTZXR0aW5nUmVxdWVzdBItCghrZXlfZW51bRgBIAEoDjIZLmJsb2NrbmluamEudjEuU2V0dGluZ0tleUgAEhQKCmtleV9zdHJpbmcYAiABKAlIABImCgV2YWx1ZRgDIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3RCCgoIa2V5X3R5cGUiPQoSU2V0U2V0dGluZ1Jlc3BvbnNlEicKB3NldHRpbmcYASABKAsyFi5ibG9ja25pbmphLnYxLlNldHRpbmciFQoTTGlzdFNldHRpbmdzUmVxdWVzdCJAChRMaXN0U2V0dGluZ3NSZXNwb25zZRIoCghzZXR0aW5ncxgBIAMoCzIWLmJsb2NrbmluamEudjEuU2V0dGluZyIYChZHZXRTaXRlU2V0dGluZ3NSZXF1ZXN0IkgKF0dldFNpdGVTZXR0aW5nc1Jlc3BvbnNlEi0KCHNldHRpbmdzGAEgASgLMhsuYmxvY2tuaW5qYS52MS5TaXRlU2V0dGluZ3MipA4KGVVwZGF0ZVNpdGVTZXR0aW5nc1JlcXVlc3QSEgoFdGl0bGUYASABKAlIAIgBARIYCgtkZXNjcmlwdGlvbhgCIAEoCUgBiAEBEh0KEGRlZmF1bHRfdGVtcGxhdGUYAyABKAlIAogBARITCgZkb21haW4YECABKAlIA4gBARIUCgdmYXZpY29uGAQgASgJSASIAQESEQoEbG9nbxgFIAEoCUgFiAEBEhUKCGxvZ29fYWx0GAYgASgJSAaIAQESHQoQYXBwbGVfdG91Y2hfaWNvbhgHIAEoCUgHiAEBEjUKDHNvY2lhbF9saW5rcxgIIAEoCzIaLmJsb2NrbmluamEudjEuU29jaWFsTGlua3NICIgBARIzCgtsZWdhbF9saW5rcxgJIAEoCzIZLmJsb2NrbmluamEudjEuTGVnYWxMaW5rc0gJiAEBEjUKDGNvbnRhY3RfaW5mbxgKIAEoCzIaLmJsb2NrbmluamEudjEuQ29udGFjdEluZm9ICogBARI1CgxzZW9fZGVmYXVsdHMYCyABKAsyGi5ibG9ja25pbmphLnYxLlNlb0RlZmF1bHRzSAuIAQESNgoJYW5hbHl0aWNzGAwgASgLMh4uYmxvY2tuaW5qYS52MS5BbmFseXRpY3NDb25maWdIDIgBARI8Cgxsb2NhbGl6YXRpb24YDSABKAsyIS5ibG9ja25pbmphLnYxLkxvY2FsaXphdGlvbkNvbmZpZ0gNiAEBEjQKCGFkdmFuY2VkGA4gASgLMh0uYmxvY2tuaW5qYS52MS5BZHZhbmNlZENvbmZpZ0gOiAEBEigKCHdlYmhvb2tzGA8gAygLMhYuYmxvY2tuaW5qYS52MS5XZWJob29rEiAKE2VuYWJsZV9hdXRob3JfcGFnZXMYESABKAhID4gBARIVCghkZXZfbW9kZRgdIAEoCEgQiAEBEhgKC2Jsb2dfcHJlZml4GBIgASgJSBGIAQESQwoPYWlfb3B0aW1pemF0aW9uGBMgASgLMiUuYmxvY2tuaW5qYS52MS5BaU9wdGltaXphdGlvbkRlZmF1bHRzSBKIAQESNgoJdHVybnN0aWxlGBQgASgLMh4uYmxvY2tuaW5qYS52MS5UdXJuc3RpbGVDb25maWdIE4gBARIgChNnb29nbGVfbWFwc19hcGlfa2V5GBUgASgJSBSIAQESHgoRcGx1Z2luX2RlcGxveV9rZXkYFyABKAlIFYgBARIjChZnb29nbGVfb2F1dGhfY2xpZW50X2lkGBggASgJSBaIAQESJwoaZ29vZ2xlX29hdXRoX2NsaWVudF9zZWNyZXQYGSABKAlIF4gBARIcCg9mYWNlYm9va19hcHBfaWQYGiABKAlIGIgBARIgChNmYWNlYm9va19hcHBfc2VjcmV0GBsgASgJSBmIAQESOwoPbW9kZXJhdGlvbl9tb2RlGBwgASgOMh0uYmxvY2tuaW5qYS52MS5Nb2RlcmF0aW9uTW9kZUgaiAEBEhsKDnZpc2lvbl9hcGlfa2V5GB4gASgJSBuIAQESHQoQcGhvdG9kbmFfYXBpX2tleRgfIAEoCUgciAEBEiEKFGNzYW1fcmVwb3J0aW5nX2VtYWlsGCAgASgJSB2IAQESIQoUcXVhcmFudGluZV90aHJlc2hvbGQYISABKAlIHogBAUIICgZfdGl0bGVCDgoMX2Rlc2NyaXB0aW9uQhMKEV9kZWZhdWx0X3RlbXBsYXRlQgkKB19kb21haW5CCgoIX2Zhdmljb25CBwoFX2xvZ29CCwoJX2xvZ29fYWx0QhMKEV9hcHBsZV90b3VjaF9pY29uQg8KDV9zb2NpYWxfbGlua3NCDgoMX2xlZ2FsX2xpbmtzQg8KDV9jb250YWN0X2luZm9CDwoNX3Nlb19kZWZhdWx0c0IMCgpfYW5hbHl0aWNzQg8KDV9sb2NhbGl6YXRpb25CCwoJX2FkdmFuY2VkQhYKFF9lbmFibGVfYXV0aG9yX3BhZ2VzQgsKCV9kZXZfbW9kZUIOCgxfYmxvZ19wcmVmaXhCEgoQX2FpX29wdGltaXphdGlvbkIMCgpfdHVybnN0aWxlQhYKFF9nb29nbGVfbWFwc19hcGlfa2V5QhQKEl9wbHVnaW5fZGVwbG95X2tleUIZChdfZ29vZ2xlX29hdXRoX2NsaWVudF9pZEIdChtfZ29vZ2xlX29hdXRoX2NsaWVudF9zZWNyZXRCEgoQX2ZhY2Vib29rX2FwcF9pZEIWChRfZmFjZWJvb2tfYXBwX3NlY3JldEISChBfbW9kZXJhdGlvbl9tb2RlQhEKD192aXNpb25fYXBpX2tleUITChFfcGhvdG9kbmFfYXBpX2tleUIXChVfY3NhbV9yZXBvcnRpbmdfZW1haWxCFwoVX3F1YXJhbnRpbmVfdGhyZXNob2xkIksKGlVwZGF0ZVNpdGVTZXR0aW5nc1Jlc3BvbnNlEi0KCHNldHRpbmdzGAEgASgLMhsuYmxvY2tuaW5qYS52MS5TaXRlU2V0dGluZ3MiHwodTGlzdEF2YWlsYWJsZVRlbXBsYXRlc1JlcXVlc3QiUAoeTGlzdEF2YWlsYWJsZVRlbXBsYXRlc1Jlc3BvbnNlEi4KCXRlbXBsYXRlcxgBIAMoCzIbLmJsb2NrbmluamEudjEuVGVtcGxhdGVJbmZvIjgKIEdldFRlbXBsYXRlU2V0dGluZ3NTY2hlbWFSZXF1ZXN0EhQKDHRlbXBsYXRlX2tleRgBIAEoCSJkCiFHZXRUZW1wbGF0ZVNldHRpbmdzU2NoZW1hUmVzcG9uc2USFAoMdGVtcGxhdGVfa2V5GAEgASgJEhMKC3NjaGVtYV9qc29uGAIgASgJEhQKDGhhc19zZXR0aW5ncxgDIAEoCCIyChpHZXRUZW1wbGF0ZVNldHRpbmdzUmVxdWVzdBIUCgx0ZW1wbGF0ZV9rZXkYASABKAkiXgobR2V0VGVtcGxhdGVTZXR0aW5nc1Jlc3BvbnNlEhQKDHRlbXBsYXRlX2tleRgBIAEoCRIpCghzZXR0aW5ncxgCIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QiYAodVXBkYXRlVGVtcGxhdGVTZXR0aW5nc1JlcXVlc3QSFAoMdGVtcGxhdGVfa2V5GAEgASgJEikKCHNldHRpbmdzGAIgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdCJhCh5VcGRhdGVUZW1wbGF0ZVNldHRpbmdzUmVzcG9uc2USFAoMdGVtcGxhdGVfa2V5GAEgASgJEikKCHNldHRpbmdzGAIgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdCI3ChhMaXN0UGFnZVRlbXBsYXRlc1JlcXVlc3QSGwoTc3lzdGVtX3RlbXBsYXRlX2tleRgBIAEoCSJxChlMaXN0UGFnZVRlbXBsYXRlc1Jlc3BvbnNlEhsKE3N5c3RlbV90ZW1wbGF0ZV9rZXkYASABKAkSNwoOcGFnZV90ZW1wbGF0ZXMYAiADKAsyHy5ibG9ja25pbmphLnYxLlBhZ2VUZW1wbGF0ZUluZm8iQgoVRXhwb3J0U2V0dGluZ3NSZXF1ZXN0EhAKCHBhc3N3b3JkGAEgASgJEhcKD2luY2x1ZGVfYWlfa2V5cxgCIAEoCCJpChZFeHBvcnRTZXR0aW5nc1Jlc3BvbnNlEhMKC2V4cG9ydF9kYXRhGAEgASgMEhAKCGZpbGVuYW1lGAIgASgJEhYKDnNldHRpbmdzX2NvdW50GAMgASgFEhAKCHdhcm5pbmdzGAQgAygJIkUKHFByZXZpZXdJbXBvcnRTZXR0aW5nc1JlcXVlc3QSEwoLaW1wb3J0X2RhdGEYASABKAwSEAoIcGFzc3dvcmQYAiABKAki0AEKHVByZXZpZXdJbXBvcnRTZXR0aW5nc1Jlc3BvbnNlEg0KBXZhbGlkGAEgASgIEg0KBWVycm9yGAIgASgJEi0KB2NoYW5nZXMYAyADKAsyHC5ibG9ja25pbmphLnYxLlNldHRpbmdDaGFuZ2USGQoRc291cmNlX3NpdGVfdGl0bGUYBCABKAkSLwoLZXhwb3J0ZWRfYXQYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhYKDmZvcm1hdF92ZXJzaW9uGAYgASgFIokBCg1TZXR0aW5nQ2hhbmdlEgsKA2tleRgBIAEoCRITCgtjaGFuZ2VfdHlwZRgCIAEoCRISCgpmaWVsZF9wYXRoGAMgASgJEhcKD2N1cnJlbnRfcHJldmlldxgEIAEoCRITCgtuZXdfcHJldmlldxgFIAEoCRIUCgxpc19zZW5zaXRpdmUYBiABKAgicQoVSW1wb3J0U2V0dGluZ3NSZXF1ZXN0EhMKC2ltcG9ydF9kYXRhGAEgASgMEhAKCHBhc3N3b3JkGAIgASgJEhsKE2NvbmZpcm1hdGlvbl9waHJhc2UYAyABKAkSFAoMaW5jbHVkZV9rZXlzGAQgAygJImYKFkltcG9ydFNldHRpbmdzUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCBIVCg1pbXBvcnRlZF9rZXlzGAIgAygJEhQKDHNraXBwZWRfa2V5cxgDIAMoCRIOCgZlcnJvcnMYBCADKAkiGAoWR2V0Q2FwYWJpbGl0aWVzUmVxdWVzdCKnAgoXR2V0Q2FwYWJpbGl0aWVzUmVzcG9uc2USDwoHcGxhbl9pZBgBIAEoCRIRCglwbGFuX25hbWUYAiABKAkSDAoEdGllchgDIAEoCRIaChJhdmFpbGFibGVfZmVhdHVyZXMYBCADKAkSNwoQZW5hYmxlZF9zaWRlY2FycxgFIAMoCzIdLmJsb2NrbmluamEudjEuRW5hYmxlZFNpZGVjYXISKQoGbGltaXRzGAYgASgLMhkuYmxvY2tuaW5qYS52MS5QbGFuTGltaXRzEhcKD2hhc19oYXJkX2xpbWl0cxgHIAEoCBISCgppc19tYW5hZ2VkGAggASgIEhgKEG9yY2hlc3RyYXRvcl91cmwYCSABKAkSEwoLaW5zdGFuY2VfaWQYCiABKAkiqwEKDkVuYWJsZWRTaWRlY2FyEhMKC2ZlYXR1cmVfa2V5GAEgASgJEgwKBGhvc3QYAiABKAkSDAoEcG9ydBgDIAEoBRI5CgZjb25maWcYBCADKAsyKS5ibG9ja25pbmphLnYxLkVuYWJsZWRTaWRlY2FyLkNvbmZpZ0VudHJ5Gi0KC0NvbmZpZ0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiXAoKUGxhbkxpbWl0cxISCgpzdG9yYWdlX2diGAEgASgFEhQKDGJhbmR3aWR0aF9nYhgCIAEoBRIRCgltYXhfcGFnZXMYAyABKAUSEQoJbWF4X3VzZXJzGAQgASgFIjIKG0VuYWJsZVNpZGVjYXJGZWF0dXJlUmVxdWVzdBITCgtmZWF0dXJlX2tleRgBIAEoCSKOAQocRW5hYmxlU2lkZWNhckZlYXR1cmVSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEjMKB3NpZGVjYXIYAiABKAsyHS5ibG9ja25pbmphLnYxLkVuYWJsZWRTaWRlY2FySACIAQESEgoFZXJyb3IYAyABKAlIAYgBAUIKCghfc2lkZWNhckIICgZfZXJyb3IiMwocRGlzYWJsZVNpZGVjYXJGZWF0dXJlUmVxdWVzdBITCgtmZWF0dXJlX2tleRgBIAEoCSJOCh1EaXNhYmxlU2lkZWNhckZlYXR1cmVSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEhIKBWVycm9yGAIgASgJSACIAQFCCAoGX2Vycm9yIhkKF0dldFN0b3JhZ2VTdGF0dXNSZXF1ZXN0IqMBChhHZXRTdG9yYWdlU3RhdHVzUmVzcG9uc2USEgoKdXNlZF9ieXRlcxgBIAEoAxITCgtsaW1pdF9ieXRlcxgCIAEoAxIUCgxwZXJjZW50X3VzZWQYAyABKAESFQoNd2FybmluZ19sZXZlbBgEIAEoCRIVCg1pc19vdmVyX2xpbWl0GAUgASgIEhoKEm92ZXJhZ2VfcmF0ZV9jZW50cxgGIAEoAypqCgpTZXR0aW5nS2V5EhsKF1NFVFRJTkdfS0VZX1VOU1BFQ0lGSUVEEAASFAoQU0VUVElOR19LRVlfU0lURRABEhIKDlNFVFRJTkdfS0VZX0FJEAISFQoRU0VUVElOR19LRVlfVEhFTUUQAypxCghTaXRlTW9kZRIZChVTSVRFX01PREVfVU5TUEVDSUZJRUQQABIUChBTSVRFX01PREVfTk9STUFMEAESGQoVU0lURV9NT0RFX01BSU5URU5BTkNFEAISGQoVU0lURV9NT0RFX0NPTUlOR19TT09OEAMqdAoOTW9kZXJhdGlvbk1vZGUSHwobTU9ERVJBVElPTl9NT0RFX1VOU1BFQ0lGSUVEEAASHwobTU9ERVJBVElPTl9NT0RFX1BSRV9QVUJMSVNIEAESIAocTU9ERVJBVElPTl9NT0RFX1BPU1RfUFVCTElTSBACMv8NCg9TZXR0aW5nc1NlcnZpY2USUQoKR2V0U2V0dGluZxIgLmJsb2NrbmluamEudjEuR2V0U2V0dGluZ1JlcXVlc3QaIS5ibG9ja25pbmphLnYxLkdldFNldHRpbmdSZXNwb25zZRJRCgpTZXRTZXR0aW5nEiAuYmxvY2tuaW5qYS52MS5TZXRTZXR0aW5nUmVxdWVzdBohLmJsb2NrbmluamEudjEuU2V0U2V0dGluZ1Jlc3BvbnNlElcKDExpc3RTZXR0aW5ncxIiLmJsb2NrbmluamEudjEuTGlzdFNldHRpbmdzUmVxdWVzdBojLmJsb2NrbmluamEudjEuTGlzdFNldHRpbmdzUmVzcG9uc2USYAoPR2V0U2l0ZVNldHRpbmdzEiUuYmxvY2tuaW5qYS52MS5HZXRTaXRlU2V0dGluZ3NSZXF1ZXN0GiYuYmxvY2tuaW5qYS52MS5HZXRTaXRlU2V0dGluZ3NSZXNwb25zZRJpChJVcGRhdGVTaXRlU2V0dGluZ3MSKC5ibG9ja25pbmphLnYxLlVwZGF0ZVNpdGVTZXR0aW5nc1JlcXVlc3QaKS5ibG9ja25pbmphLnYxLlVwZGF0ZVNpdGVTZXR0aW5nc1Jlc3BvbnNlEnUKFkxpc3RBdmFpbGFibGVUZW1wbGF0ZXMSLC5ibG9ja25pbmphLnYxLkxpc3RBdmFpbGFibGVUZW1wbGF0ZXNSZXF1ZXN0Gi0uYmxvY2tuaW5qYS52MS5MaXN0QXZhaWxhYmxlVGVtcGxhdGVzUmVzcG9uc2USfgoZR2V0VGVtcGxhdGVTZXR0aW5nc1NjaGVtYRIvLmJsb2NrbmluamEudjEuR2V0VGVtcGxhdGVTZXR0aW5nc1NjaGVtYVJlcXVlc3QaMC5ibG9ja25pbmphLnYxLkdldFRlbXBsYXRlU2V0dGluZ3NTY2hlbWFSZXNwb25zZRJsChNHZXRUZW1wbGF0ZVNldHRpbmdzEikuYmxvY2tuaW5qYS52MS5HZXRUZW1wbGF0ZVNldHRpbmdzUmVxdWVzdBoqLmJsb2NrbmluamEudjEuR2V0VGVtcGxhdGVTZXR0aW5nc1Jlc3BvbnNlEnUKFlVwZGF0ZVRlbXBsYXRlU2V0dGluZ3MSLC5ibG9ja25pbmphLnYxLlVwZGF0ZVRlbXBsYXRlU2V0dGluZ3NSZXF1ZXN0Gi0uYmxvY2tuaW5qYS52MS5VcGRhdGVUZW1wbGF0ZVNldHRpbmdzUmVzcG9uc2USZgoRTGlzdFBhZ2VUZW1wbGF0ZXMSJy5ibG9ja25pbmphLnYxLkxpc3RQYWdlVGVtcGxhdGVzUmVxdWVzdBooLmJsb2NrbmluamEudjEuTGlzdFBhZ2VUZW1wbGF0ZXNSZXNwb25zZRJdCg5FeHBvcnRTZXR0aW5ncxIkLmJsb2NrbmluamEudjEuRXhwb3J0U2V0dGluZ3NSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5FeHBvcnRTZXR0aW5nc1Jlc3BvbnNlEnIKFVByZXZpZXdJbXBvcnRTZXR0aW5ncxIrLmJsb2NrbmluamEudjEuUHJldmlld0ltcG9ydFNldHRpbmdzUmVxdWVzdBosLmJsb2NrbmluamEudjEuUHJldmlld0ltcG9ydFNldHRpbmdzUmVzcG9uc2USXQoOSW1wb3J0U2V0dGluZ3MSJC5ibG9ja25pbmphLnYxLkltcG9ydFNldHRpbmdzUmVxdWVzdBolLmJsb2NrbmluamEudjEuSW1wb3J0U2V0dGluZ3NSZXNwb25zZRJjChBHZXRTdG9yYWdlU3RhdHVzEiYuYmxvY2tuaW5qYS52MS5HZXRTdG9yYWdlU3RhdHVzUmVxdWVzdBonLmJsb2NrbmluamEudjEuR2V0U3RvcmFnZVN0YXR1c1Jlc3BvbnNlEmAKD0dldENhcGFiaWxpdGllcxIlLmJsb2NrbmluamEudjEuR2V0Q2FwYWJpbGl0aWVzUmVxdWVzdBomLmJsb2NrbmluamEudjEuR2V0Q2FwYWJpbGl0aWVzUmVzcG9uc2USbwoURW5hYmxlU2lkZWNhckZlYXR1cmUSKi5ibG9ja25pbmphLnYxLkVuYWJsZVNpZGVjYXJGZWF0dXJlUmVxdWVzdBorLmJsb2NrbmluamEudjEuRW5hYmxlU2lkZWNhckZlYXR1cmVSZXNwb25zZRJyChVEaXNhYmxlU2lkZWNhckZlYXR1cmUSKy5ibG9ja25pbmphLnYxLkRpc2FibGVTaWRlY2FyRmVhdHVyZVJlcXVlc3QaLC5ibG9ja25pbmphLnYxLkRpc2FibGVTaWRlY2FyRmVhdHVyZVJlc3BvbnNlQsMBChFjb20uYmxvY2tuaW5qYS52MUINU2V0dGluZ3NQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_google_protobuf_struct, file_google_protobuf_timestamp], +); +/** + * Well-known setting keys + * + * @generated from enum blockninja.v1.SettingKey + */ +var SettingKey; +(function (SettingKey) { + /** + * @generated from enum value: SETTING_KEY_UNSPECIFIED = 0; + */ + SettingKey[(SettingKey["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * Site-wide settings (title, description, default_template) + * + * @generated from enum value: SETTING_KEY_SITE = 1; + */ + SettingKey[(SettingKey["SITE"] = 1)] = "SITE"; + /** + * AI provider settings + * + * @generated from enum value: SETTING_KEY_AI = 2; + */ + SettingKey[(SettingKey["AI"] = 2)] = "AI"; + /** + * Active theme configuration + * + * @generated from enum value: SETTING_KEY_THEME = 3; + */ + SettingKey[(SettingKey["THEME"] = 3)] = "THEME"; +})(SettingKey || (SettingKey = {})); +/** + * Site operating mode + * + * @generated from enum blockninja.v1.SiteMode + */ +var SiteMode; +(function (SiteMode) { + /** + * Defaults to NORMAL + * + * @generated from enum value: SITE_MODE_UNSPECIFIED = 0; + */ + SiteMode[(SiteMode["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * Site operates normally + * + * @generated from enum value: SITE_MODE_NORMAL = 1; + */ + SiteMode[(SiteMode["NORMAL"] = 1)] = "NORMAL"; + /** + * Show maintenance page to visitors + * + * @generated from enum value: SITE_MODE_MAINTENANCE = 2; + */ + SiteMode[(SiteMode["MAINTENANCE"] = 2)] = "MAINTENANCE"; + /** + * Show coming soon page to visitors + * + * @generated from enum value: SITE_MODE_COMING_SOON = 3; + */ + SiteMode[(SiteMode["COMING_SOON"] = 3)] = "COMING_SOON"; +})(SiteMode || (SiteMode = {})); +/** + * Media moderation workflow mode + * + * @generated from enum blockninja.v1.ModerationMode + */ +var ModerationMode; +(function (ModerationMode) { + /** + * Defaults to PRE_PUBLISH + * + * @generated from enum value: MODERATION_MODE_UNSPECIFIED = 0; + */ + ModerationMode[(ModerationMode["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * Clean uploads wait for moderator approval + * + * @generated from enum value: MODERATION_MODE_PRE_PUBLISH = 1; + */ + ModerationMode[(ModerationMode["PRE_PUBLISH"] = 1)] = "PRE_PUBLISH"; + /** + * Clean uploads are visible immediately, then reviewed + * + * @generated from enum value: MODERATION_MODE_POST_PUBLISH = 2; + */ + ModerationMode[(ModerationMode["POST_PUBLISH"] = 2)] = "POST_PUBLISH"; +})(ModerationMode || (ModerationMode = {})); +/** + * @generated from service blockninja.v1.SettingsService + */ +const SettingsService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_settings, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/settings.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Get a single setting by key + * + * @generated from rpc blockninja.v1.SettingsService.GetSetting + */ +SettingsService.method.getSetting; +/** + * Set a setting value + * + * @generated from rpc blockninja.v1.SettingsService.SetSetting + */ +SettingsService.method.setSetting; +/** + * List all settings + * + * @generated from rpc blockninja.v1.SettingsService.ListSettings + */ +SettingsService.method.listSettings; +/** + * Get site settings (convenience method) + * + * @generated from rpc blockninja.v1.SettingsService.GetSiteSettings + */ +SettingsService.method.getSiteSettings; +/** + * Update site settings + * + * @generated from rpc blockninja.v1.SettingsService.UpdateSiteSettings + */ +SettingsService.method.updateSiteSettings; +/** + * List available templates (loaded plugins) + * + * @generated from rpc blockninja.v1.SettingsService.ListAvailableTemplates + */ +SettingsService.method.listAvailableTemplates; +/** + * Get template settings schema + * + * @generated from rpc blockninja.v1.SettingsService.GetTemplateSettingsSchema + */ +SettingsService.method.getTemplateSettingsSchema; +/** + * Get template settings values + * + * @generated from rpc blockninja.v1.SettingsService.GetTemplateSettings + */ +SettingsService.method.getTemplateSettings; +/** + * Update template settings + * + * @generated from rpc blockninja.v1.SettingsService.UpdateTemplateSettings + */ +SettingsService.method.updateTemplateSettings; +/** + * List available page templates for a system template (theme) + * + * @generated from rpc blockninja.v1.SettingsService.ListPageTemplates + */ +SettingsService.method.listPageTemplates; +/** + * Export all settings to encrypted JSON + * + * @generated from rpc blockninja.v1.SettingsService.ExportSettings + */ +SettingsService.method.exportSettings; +/** + * Preview what an import would change (dry run) + * + * @generated from rpc blockninja.v1.SettingsService.PreviewImportSettings + */ +SettingsService.method.previewImportSettings; +/** + * Import settings from encrypted JSON + * + * @generated from rpc blockninja.v1.SettingsService.ImportSettings + */ +SettingsService.method.importSettings; +/** + * Get storage usage and limits (for storage gauge in media library) + * + * @generated from rpc blockninja.v1.SettingsService.GetStorageStatus + */ +const getStorageStatus = SettingsService.method.getStorageStatus; +/** + * Get plan capabilities and enabled features + * + * @generated from rpc blockninja.v1.SettingsService.GetCapabilities + */ +SettingsService.method.getCapabilities; +/** + * Enable a sidecar feature (requires Pro+ plan and orchestrator) + * + * @generated from rpc blockninja.v1.SettingsService.EnableSidecarFeature + */ +SettingsService.method.enableSidecarFeature; +/** + * Disable a sidecar feature + * + * @generated from rpc blockninja.v1.SettingsService.DisableSidecarFeature + */ +SettingsService.method.disableSidecarFeature; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/setup.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/setup.proto. + */ +const file_blockninja_v1_setup = /*@__PURE__*/ fileDesc( + "ChlibG9ja25pbmphL3YxL3NldHVwLnByb3RvEg1ibG9ja25pbmphLnYxIhcKFUdldFNldHVwU3RhdHVzUmVxdWVzdCKYAQoWR2V0U2V0dXBTdGF0dXNSZXNwb25zZRIWCg5zZXR1cF9yZXF1aXJlZBgBIAEoCBIZChFvcmNoZXN0cmF0b3JfbW9kZRgCIAEoCBIcChRvcmNoZXN0cmF0b3Jfc3NvX3VybBgDIAEoCRIaChJkZWZhdWx0X3NpdGVfdGl0bGUYBCABKAkSEQoJaGFzX3VzZXJzGAUgASgIIt0CChRDb21wbGV0ZVNldHVwUmVxdWVzdBINCgVlbWFpbBgBIAEoCRIQCghwYXNzd29yZBgCIAEoCRISCgpmaXJzdF9uYW1lGAMgASgJEhEKCWxhc3RfbmFtZRgEIAEoCRIXCgpzaXRlX3RpdGxlGAUgASgJSACIAQESHQoQc2l0ZV9kZXNjcmlwdGlvbhgGIAEoCUgBiAEBEhgKC3NpdGVfZG9tYWluGAcgASgJSAKIAQESGAoLYmFja3VwX2RhdGEYCCABKAxIA4gBARIcCg9iYWNrdXBfcGFzc3dvcmQYCSABKAlIBIgBARIbChNjcmVhdGVfc2VlZF9jb250ZW50GAogASgIQg0KC19zaXRlX3RpdGxlQhMKEV9zaXRlX2Rlc2NyaXB0aW9uQg4KDF9zaXRlX2RvbWFpbkIOCgxfYmFja3VwX2RhdGFCEgoQX2JhY2t1cF9wYXNzd29yZCJxChVDb21wbGV0ZVNldHVwUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCBImCgR1c2VyGAIgASgLMhguYmxvY2tuaW5qYS52MS5BZG1pblVzZXISEAoId2FybmluZ3MYAyADKAkSDQoFZXJyb3IYBCABKAkivAEKF0NvbXBsZXRlU1NPU2V0dXBSZXF1ZXN0EhcKCnNpdGVfdGl0bGUYASABKAlIAIgBARIdChBzaXRlX2Rlc2NyaXB0aW9uGAIgASgJSAGIAQESGAoLc2l0ZV9kb21haW4YAyABKAlIAogBARIbChNjcmVhdGVfc2VlZF9jb250ZW50GAQgASgIQg0KC19zaXRlX3RpdGxlQhMKEV9zaXRlX2Rlc2NyaXB0aW9uQg4KDF9zaXRlX2RvbWFpbiJMChhDb21wbGV0ZVNTT1NldHVwUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCBIQCgh3YXJuaW5ncxgCIAMoCRINCgVlcnJvchgDIAEoCSJCChlQcmV2aWV3U2V0dXBCYWNrdXBSZXF1ZXN0EhMKC2JhY2t1cF9kYXRhGAEgASgMEhAKCHBhc3N3b3JkGAIgASgJIs0BChpQcmV2aWV3U2V0dXBCYWNrdXBSZXNwb25zZRINCgV2YWxpZBgBIAEoCBINCgVlcnJvchgCIAEoCRIZChFzb3VyY2Vfc2l0ZV90aXRsZRgDIAEoCRIvCgtleHBvcnRlZF9hdBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFgoOZm9ybWF0X3ZlcnNpb24YBSABKAUSLQoHc3VtbWFyeRgGIAEoCzIcLmJsb2NrbmluamEudjEuQmFja3VwU3VtbWFyeTKZAwoMU2V0dXBTZXJ2aWNlEl0KDkdldFNldHVwU3RhdHVzEiQuYmxvY2tuaW5qYS52MS5HZXRTZXR1cFN0YXR1c1JlcXVlc3QaJS5ibG9ja25pbmphLnYxLkdldFNldHVwU3RhdHVzUmVzcG9uc2USWgoNQ29tcGxldGVTZXR1cBIjLmJsb2NrbmluamEudjEuQ29tcGxldGVTZXR1cFJlcXVlc3QaJC5ibG9ja25pbmphLnYxLkNvbXBsZXRlU2V0dXBSZXNwb25zZRJjChBDb21wbGV0ZVNTT1NldHVwEiYuYmxvY2tuaW5qYS52MS5Db21wbGV0ZVNTT1NldHVwUmVxdWVzdBonLmJsb2NrbmluamEudjEuQ29tcGxldGVTU09TZXR1cFJlc3BvbnNlEmkKElByZXZpZXdTZXR1cEJhY2t1cBIoLmJsb2NrbmluamEudjEuUHJldmlld1NldHVwQmFja3VwUmVxdWVzdBopLmJsb2NrbmluamEudjEuUHJldmlld1NldHVwQmFja3VwUmVzcG9uc2VCwAEKEWNvbS5ibG9ja25pbmphLnYxQgpTZXR1cFByb3RvUAFaSmdpdC5kZXYuYWxleGR1bm1vdy5jb20vYmxvY2svbmluamEvaW50ZXJuYWwvYXBpL2Jsb2NrbmluamEvdjE7YmxvY2tuaW5qYXYxogIDQlhYqgINQmxvY2tuaW5qYS5WMcoCDUJsb2NrbmluamFcVjHiAhlCbG9ja25pbmphXFYxXEdQQk1ldGFkYXRh6gIOQmxvY2tuaW5qYTo6VjFiBnByb3RvMw", + [file_blockninja_v1_auth, file_blockninja_v1_backup, file_google_protobuf_timestamp], +); +/** + * SetupService handles first-time setup when no admin users exist. + * All endpoints are PUBLIC (no auth required) but CompleteSetup only works + * when zero admin users exist in the database. + * When orchestrator mode is enabled, setup requires SSO authentication. + * + * @generated from service blockninja.v1.SetupService + */ +const SetupService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_setup, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/setup.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Check if setup is required (no admin users exist) + * + * @generated from rpc blockninja.v1.SetupService.GetSetupStatus + */ +SetupService.method.getSetupStatus; +/** + * Complete the initial setup - creates first admin and configures site + * SECURITY: Only works when no admin users exist + * + * @generated from rpc blockninja.v1.SetupService.CompleteSetup + */ +SetupService.method.completeSetup; +/** + * Complete SSO setup - applies site settings after SSO creates the user + * SECURITY: Requires authentication, only works when exactly 1 admin exists + * + * @generated from rpc blockninja.v1.SetupService.CompleteSSOSetup + */ +SetupService.method.completeSSOSetup; +/** + * Preview a backup during setup (before user exists) + * + * @generated from rpc blockninja.v1.SetupService.PreviewSetupBackup + */ +SetupService.method.previewSetupBackup; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/smart_block.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/smart_block.proto. + */ +const file_blockninja_v1_smart_block = /*@__PURE__*/ fileDesc( + "Ch9ibG9ja25pbmphL3YxL3NtYXJ0X2Jsb2NrLnByb3RvEg1ibG9ja25pbmphLnYxIlsKGUdlbmVyYXRlU21hcnRCbG9ja1JlcXVlc3QSDgoGcHJvbXB0GAEgASgJEhUKDWV4aXN0aW5nX2h0bWwYAiABKAkSFwoPZXhpc3Rpbmdfc2NoZW1hGAMgASgJInQKGkdlbmVyYXRlU21hcnRCbG9ja1Jlc3BvbnNlEhUKDWdlbmVyYXRpb25faWQYASABKAkSEwoLc2NoZW1hX2pzb24YAiABKAkSFQoNaHRtbF90ZW1wbGF0ZRgDIAEoCRITCgtleHBsYW5hdGlvbhgEIAEoCSI0ChNDb252ZXJzYXRpb25NZXNzYWdlEgwKBHJvbGUYASABKAkSDwoHY29udGVudBgCIAEoCSKtAQoXQXNzaXN0U21hcnRCbG9ja1JlcXVlc3QSEwoLaW5zdHJ1Y3Rpb24YASABKAkSFAoMY3VycmVudF9odG1sGAIgASgJEhMKC3NjaGVtYV9qc29uGAMgASgJEkAKFGNvbnZlcnNhdGlvbl9oaXN0b3J5GAQgAygLMiIuYmxvY2tuaW5qYS52MS5Db252ZXJzYXRpb25NZXNzYWdlEhAKCGJsb2NrX2lkGAUgASgJInwKGEFzc2lzdFNtYXJ0QmxvY2tSZXNwb25zZRIVCg1odG1sX3RlbXBsYXRlGAEgASgJEhMKC2V4cGxhbmF0aW9uGAIgASgJEjQKEGRldGVjdGVkX3JlbmFtZXMYAyADKAsyGi5ibG9ja25pbmphLnYxLkZpZWxkUmVuYW1lIlYKF0dlbmVyYXRlVGVzdERhdGFSZXF1ZXN0EhMKC3NjaGVtYV9qc29uGAEgASgJEg8KB2NvbnRleHQYAiABKAkSFQoNaHRtbF90ZW1wbGF0ZRgDIAEoCSJHChhHZW5lcmF0ZVRlc3REYXRhUmVzcG9uc2USFgoOdGVzdF9kYXRhX2pzb24YASABKAkSEwoLZXhwbGFuYXRpb24YAiABKAkiWwoTR2VuZXJhdGVUZXh0UmVxdWVzdBIOCgZwcm9tcHQYASABKAkSDwoHY29udGV4dBgCIAEoCRIjCgR0YXNrGAMgASgOMhUuYmxvY2tuaW5qYS52MS5BSVRhc2siOQoUR2VuZXJhdGVUZXh0UmVzcG9uc2USDAoEdGV4dBgBIAEoCRITCgtleHBsYW5hdGlvbhgCIAEoCTKhAwoRU21hcnRCbG9ja1NlcnZpY2USaQoSR2VuZXJhdGVTbWFydEJsb2NrEiguYmxvY2tuaW5qYS52MS5HZW5lcmF0ZVNtYXJ0QmxvY2tSZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5HZW5lcmF0ZVNtYXJ0QmxvY2tSZXNwb25zZRJjChBBc3Npc3RTbWFydEJsb2NrEiYuYmxvY2tuaW5qYS52MS5Bc3Npc3RTbWFydEJsb2NrUmVxdWVzdBonLmJsb2NrbmluamEudjEuQXNzaXN0U21hcnRCbG9ja1Jlc3BvbnNlEmMKEEdlbmVyYXRlVGVzdERhdGESJi5ibG9ja25pbmphLnYxLkdlbmVyYXRlVGVzdERhdGFSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5HZW5lcmF0ZVRlc3REYXRhUmVzcG9uc2USVwoMR2VuZXJhdGVUZXh0EiIuYmxvY2tuaW5qYS52MS5HZW5lcmF0ZVRleHRSZXF1ZXN0GiMuYmxvY2tuaW5qYS52MS5HZW5lcmF0ZVRleHRSZXNwb25zZULFAQoRY29tLmJsb2NrbmluamEudjFCD1NtYXJ0QmxvY2tQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_blockninja_v1_ai, file_blockninja_v1_blocks], +); +/** + * SmartBlockService handles AI-powered block generation and assistance + * + * @generated from service blockninja.v1.SmartBlockService + */ +const SmartBlockService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_smart_block, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/smart_block.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Generate schema + HTML template from a natural language prompt + * + * @generated from rpc blockninja.v1.SmartBlockService.GenerateSmartBlock + */ +SmartBlockService.method.generateSmartBlock; +/** + * AI assist - modify HTML based on instruction + * + * @generated from rpc blockninja.v1.SmartBlockService.AssistSmartBlock + */ +SmartBlockService.method.assistSmartBlock; +/** + * Generate realistic test/filler data based on schema + * + * @generated from rpc blockninja.v1.SmartBlockService.GenerateTestData + */ +SmartBlockService.method.generateTestData; +/** + * Generate text content from a prompt (for site status messages, etc.) + * + * @generated from rpc blockninja.v1.SmartBlockService.GenerateText + */ +SmartBlockService.method.generateText; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/storage.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/storage.proto. + */ +const file_blockninja_v1_storage = /*@__PURE__*/ fileDesc( + "ChtibG9ja25pbmphL3YxL3N0b3JhZ2UucHJvdG8SDWJsb2NrbmluamEudjEi3QEKCFMzQ29uZmlnEg4KBmJ1Y2tldBgBIAEoCRIOCgZyZWdpb24YAiABKAkSFQoNYWNjZXNzX2tleV9pZBgDIAEoCRIZChFzZWNyZXRfYWNjZXNzX2tleRgEIAEoCRIUCgxlbmRwb2ludF91cmwYBSABKAkSEgoKcGF0aF9zdHlsZRgGIAEoCBIPCgdjZG5fdXJsGAcgASgJEhAKCGlzX3ZhbGlkGAggASgIEjIKDmxhc3RfdGVzdGVkX2F0GAkgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCIUChJHZXRTM0NvbmZpZ1JlcXVlc3QiUgoTR2V0UzNDb25maWdSZXNwb25zZRISCgpjb25maWd1cmVkGAEgASgIEicKBmNvbmZpZxgCIAEoCzIXLmJsb2NrbmluamEudjEuUzNDb25maWciPgoTU2F2ZVMzQ29uZmlnUmVxdWVzdBInCgZjb25maWcYASABKAsyFy5ibG9ja25pbmphLnYxLlMzQ29uZmlnIjYKFFNhdmVTM0NvbmZpZ1Jlc3BvbnNlEg8KB3N1Y2Nlc3MYASABKAgSDQoFZXJyb3IYAiABKAkiQgoXVGVzdFMzQ29ubmVjdGlvblJlcXVlc3QSJwoGY29uZmlnGAEgASgLMhcuYmxvY2tuaW5qYS52MS5TM0NvbmZpZyJTChhUZXN0UzNDb25uZWN0aW9uUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCBINCgVlcnJvchgCIAEoCRIXCg9idWNrZXRfbG9jYXRpb24YAyABKAkiFwoVRGVsZXRlUzNDb25maWdSZXF1ZXN0IikKFkRlbGV0ZVMzQ29uZmlnUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCDKDAwoOU3RvcmFnZVNlcnZpY2USVAoLR2V0UzNDb25maWcSIS5ibG9ja25pbmphLnYxLkdldFMzQ29uZmlnUmVxdWVzdBoiLmJsb2NrbmluamEudjEuR2V0UzNDb25maWdSZXNwb25zZRJXCgxTYXZlUzNDb25maWcSIi5ibG9ja25pbmphLnYxLlNhdmVTM0NvbmZpZ1JlcXVlc3QaIy5ibG9ja25pbmphLnYxLlNhdmVTM0NvbmZpZ1Jlc3BvbnNlEmMKEFRlc3RTM0Nvbm5lY3Rpb24SJi5ibG9ja25pbmphLnYxLlRlc3RTM0Nvbm5lY3Rpb25SZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5UZXN0UzNDb25uZWN0aW9uUmVzcG9uc2USXQoORGVsZXRlUzNDb25maWcSJC5ibG9ja25pbmphLnYxLkRlbGV0ZVMzQ29uZmlnUmVxdWVzdBolLmJsb2NrbmluamEudjEuRGVsZXRlUzNDb25maWdSZXNwb25zZULCAQoRY29tLmJsb2NrbmluamEudjFCDFN0b3JhZ2VQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_google_protobuf_timestamp], +); +/** + * StorageService handles S3/cloud storage configuration + * + * @generated from service blockninja.v1.StorageService + */ +const StorageService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_storage, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/storage.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Get current S3 configuration (credentials masked) + * + * @generated from rpc blockninja.v1.StorageService.GetS3Config + */ +StorageService.method.getS3Config; +/** + * Save S3 configuration (tests connection before saving) + * + * @generated from rpc blockninja.v1.StorageService.SaveS3Config + */ +StorageService.method.saveS3Config; +/** + * Test S3 connection without saving + * + * @generated from rpc blockninja.v1.StorageService.TestS3Connection + */ +StorageService.method.testS3Connection; +/** + * Delete S3 configuration + * + * @generated from rpc blockninja.v1.StorageService.DeleteS3Config + */ +StorageService.method.deleteS3Config; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/tags.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/tags.proto. + */ +const file_blockninja_v1_tags = /*@__PURE__*/ fileDesc( + "ChhibG9ja25pbmphL3YxL3RhZ3MucHJvdG8SDWJsb2NrbmluamEudjEigQEKA1RhZxIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEgwKBHNsdWcYAyABKAkSDQoFY29sb3IYBCABKAkSEwoLdXNhZ2VfY291bnQYBSABKAUSLgoKY3JlYXRlZF9hdBgGIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiVgoPTGlzdFRhZ3NSZXF1ZXN0EjMKC2VudGl0eV90eXBlGAEgASgOMhkuYmxvY2tuaW5qYS52MS5FbnRpdHlUeXBlSACIAQFCDgoMX2VudGl0eV90eXBlIjQKEExpc3RUYWdzUmVzcG9uc2USIAoEdGFncxgBIAMoCzISLmJsb2NrbmluamEudjEuVGFnIj4KEENyZWF0ZVRhZ1JlcXVlc3QSDAoEbmFtZRgBIAEoCRISCgVjb2xvchgCIAEoCUgAiAEBQggKBl9jb2xvciI0ChFDcmVhdGVUYWdSZXNwb25zZRIfCgN0YWcYASABKAsyEi5ibG9ja25pbmphLnYxLlRhZyJYChBVcGRhdGVUYWdSZXF1ZXN0EgoKAmlkGAEgASgJEhEKBG5hbWUYAiABKAlIAIgBARISCgVjb2xvchgDIAEoCUgBiAEBQgcKBV9uYW1lQggKBl9jb2xvciI0ChFVcGRhdGVUYWdSZXNwb25zZRIfCgN0YWcYASABKAsyEi5ibG9ja25pbmphLnYxLlRhZyIeChBEZWxldGVUYWdSZXF1ZXN0EgoKAmlkGAEgASgJIhMKEURlbGV0ZVRhZ1Jlc3BvbnNlIlkKFEdldEVudGl0eVRhZ3NSZXF1ZXN0Ei4KC2VudGl0eV90eXBlGAEgASgOMhkuYmxvY2tuaW5qYS52MS5FbnRpdHlUeXBlEhEKCWVudGl0eV9pZBgCIAEoCSI5ChVHZXRFbnRpdHlUYWdzUmVzcG9uc2USIAoEdGFncxgBIAMoCzISLmJsb2NrbmluamEudjEuVGFnImoKFFNldEVudGl0eVRhZ3NSZXF1ZXN0Ei4KC2VudGl0eV90eXBlGAEgASgOMhkuYmxvY2tuaW5qYS52MS5FbnRpdHlUeXBlEhEKCWVudGl0eV9pZBgCIAEoCRIPCgd0YWdfaWRzGAMgAygJIjkKFVNldEVudGl0eVRhZ3NSZXNwb25zZRIgCgR0YWdzGAEgAygLMhIuYmxvY2tuaW5qYS52MS5UYWcqbQoKRW50aXR5VHlwZRIbChdFTlRJVFlfVFlQRV9VTlNQRUNJRklFRBAAEhUKEUVOVElUWV9UWVBFX01FRElBEAESFAoQRU5USVRZX1RZUEVfUEFHRRACEhUKEUVOVElUWV9UWVBFX0JMT0NLEAMygQQKClRhZ1NlcnZpY2USSwoITGlzdFRhZ3MSHi5ibG9ja25pbmphLnYxLkxpc3RUYWdzUmVxdWVzdBofLmJsb2NrbmluamEudjEuTGlzdFRhZ3NSZXNwb25zZRJOCglDcmVhdGVUYWcSHy5ibG9ja25pbmphLnYxLkNyZWF0ZVRhZ1JlcXVlc3QaIC5ibG9ja25pbmphLnYxLkNyZWF0ZVRhZ1Jlc3BvbnNlEk4KCVVwZGF0ZVRhZxIfLmJsb2NrbmluamEudjEuVXBkYXRlVGFnUmVxdWVzdBogLmJsb2NrbmluamEudjEuVXBkYXRlVGFnUmVzcG9uc2USTgoJRGVsZXRlVGFnEh8uYmxvY2tuaW5qYS52MS5EZWxldGVUYWdSZXF1ZXN0GiAuYmxvY2tuaW5qYS52MS5EZWxldGVUYWdSZXNwb25zZRJaCg1HZXRFbnRpdHlUYWdzEiMuYmxvY2tuaW5qYS52MS5HZXRFbnRpdHlUYWdzUmVxdWVzdBokLmJsb2NrbmluamEudjEuR2V0RW50aXR5VGFnc1Jlc3BvbnNlEloKDVNldEVudGl0eVRhZ3MSIy5ibG9ja25pbmphLnYxLlNldEVudGl0eVRhZ3NSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5TZXRFbnRpdHlUYWdzUmVzcG9uc2VCvwEKEWNvbS5ibG9ja25pbmphLnYxQglUYWdzUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_google_protobuf_timestamp], +); +/** + * Entity types that can be tagged + * + * @generated from enum blockninja.v1.EntityType + */ +var EntityType; +(function (EntityType) { + /** + * @generated from enum value: ENTITY_TYPE_UNSPECIFIED = 0; + */ + EntityType[(EntityType["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * @generated from enum value: ENTITY_TYPE_MEDIA = 1; + */ + EntityType[(EntityType["MEDIA"] = 1)] = "MEDIA"; + /** + * @generated from enum value: ENTITY_TYPE_PAGE = 2; + */ + EntityType[(EntityType["PAGE"] = 2)] = "PAGE"; + /** + * @generated from enum value: ENTITY_TYPE_BLOCK = 3; + */ + EntityType[(EntityType["BLOCK"] = 3)] = "BLOCK"; +})(EntityType || (EntityType = {})); +/** + * Generic tagging service for any entity type + * + * @generated from service blockninja.v1.TagService + */ +const TagService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_tags, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/tags.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * List all tags, optionally filtered by entity type + * + * @generated from rpc blockninja.v1.TagService.ListTags + */ +TagService.method.listTags; +/** + * Create a new tag + * + * @generated from rpc blockninja.v1.TagService.CreateTag + */ +TagService.method.createTag; +/** + * Update a tag + * + * @generated from rpc blockninja.v1.TagService.UpdateTag + */ +TagService.method.updateTag; +/** + * Delete a tag + * + * @generated from rpc blockninja.v1.TagService.DeleteTag + */ +TagService.method.deleteTag; +/** + * Get tags for a specific entity + * + * @generated from rpc blockninja.v1.TagService.GetEntityTags + */ +TagService.method.getEntityTags; +/** + * Set tags for an entity (replaces all existing tags) + * + * @generated from rpc blockninja.v1.TagService.SetEntityTags + */ +TagService.method.setEntityTags; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/theme.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/theme.proto. + */ +const file_blockninja_v1_theme = /*@__PURE__*/ fileDesc( + "ChlibG9ja25pbmphL3YxL3RoZW1lLnByb3RvEg1ibG9ja25pbmphLnYxIp0DCgtDb2xvclNjaGVtZRISCgpiYWNrZ3JvdW5kGAEgASgJEhIKCmZvcmVncm91bmQYAiABKAkSDAoEY2FyZBgDIAEoCRIXCg9jYXJkX2ZvcmVncm91bmQYBCABKAkSDwoHcG9wb3ZlchgFIAEoCRIaChJwb3BvdmVyX2ZvcmVncm91bmQYBiABKAkSDwoHcHJpbWFyeRgHIAEoCRIaChJwcmltYXJ5X2ZvcmVncm91bmQYCCABKAkSEQoJc2Vjb25kYXJ5GAkgASgJEhwKFHNlY29uZGFyeV9mb3JlZ3JvdW5kGAogASgJEg0KBW11dGVkGAsgASgJEhgKEG11dGVkX2ZvcmVncm91bmQYDCABKAkSDgoGYWNjZW50GA0gASgJEhkKEWFjY2VudF9mb3JlZ3JvdW5kGA4gASgJEhMKC2Rlc3RydWN0aXZlGA8gASgJEh4KFmRlc3RydWN0aXZlX2ZvcmVncm91bmQYECABKAkSDgoGYm9yZGVyGBEgASgJEg0KBWlucHV0GBIgASgJEgwKBHJpbmcYEyABKAkiVAoLQ3VzdG9tQ29sb3ISDAoEbmFtZRgBIAEoCRITCgtsaWdodF92YWx1ZRgCIAEoCRISCgpkYXJrX3ZhbHVlGAMgASgJEg4KBnNvdXJjZRgEIAEoCSKZAQoPVGhlbWVUeXBvZ3JhcGh5EhQKDGZvbnRfaGVhZGluZxgBIAEoCRIRCglmb250X2JvZHkYAiABKAkSEQoJZm9udF9tb25vGAMgASgJEhYKDmZvbnRfc2l6ZV9iYXNlGAQgASgJEhgKEGxpbmVfaGVpZ2h0X2Jhc2UYBSABKAkSGAoQZm9udF93ZWlnaHRfYmFzZRgGIAEoCSK8AQoMVGhlbWVTcGFjaW5nEg4KBnJhZGl1cxgBIAEoCRIVCg1idXR0b25fcmFkaXVzGAIgASgJEhMKC2NhcmRfcmFkaXVzGAMgASgJEhQKDGlucHV0X3JhZGl1cxgEIAEoCRIUCgxwaWxsX2J1dHRvbnMYBSABKAgSFAoMYm9yZGVyX3dpZHRoGAYgASgJEhcKD2NvbnRhaW5lcl93aWR0aBgHIAEoCRIVCg1zcGFjaW5nX3NjYWxlGAggASgJIrcBCgxUaGVtZUVmZmVjdHMSGAoQc2hhZG93X2ludGVuc2l0eRgBIAEoBRIUCgxzaGFkb3dfY29sb3IYAiABKAkSEwoLc2hhZG93X2JsdXIYAyABKAkSGAoQZWxldmF0aW9uX3ByZXNldBgEIAEoCRIYChB0cmFuc2l0aW9uX3NwZWVkGAUgASgJEhUKDXJlZHVjZV9tb3Rpb24YBiABKAgSFwoPaG92ZXJfaW50ZW5zaXR5GAcgASgJIrgBChBCdXR0b25UeXBlQ29uZmlnEhAKCGJnX2NvbG9yGAEgASgJEhIKCnRleHRfY29sb3IYAiABKAkSFAoMYm9yZGVyX2NvbG9yGAMgASgJEhQKDGJvcmRlcl93aWR0aBgEIAEoCRIOCgZyYWRpdXMYBSABKAkSDgoGc2hhZG93GAYgASgJEhAKCGhvdmVyX2JnGAcgASgJEhIKCmhvdmVyX3RleHQYCCABKAkSDAoEc2l6ZRgJIAEoCSLEAwoMVGhlbWVCdXR0b25zEjAKB3ByaW1hcnkYASABKAsyHy5ibG9ja25pbmphLnYxLkJ1dHRvblR5cGVDb25maWcSMgoJc2Vjb25kYXJ5GAIgASgLMh8uYmxvY2tuaW5qYS52MS5CdXR0b25UeXBlQ29uZmlnEjAKB291dGxpbmUYAyABKAsyHy5ibG9ja25pbmphLnYxLkJ1dHRvblR5cGVDb25maWcSLgoFZ2hvc3QYBCABKAsyHy5ibG9ja25pbmphLnYxLkJ1dHRvblR5cGVDb25maWcSLQoEbGluaxgFIAEoCzIfLmJsb2NrbmluamEudjEuQnV0dG9uVHlwZUNvbmZpZxI0CgtkZXN0cnVjdGl2ZRgGIAEoCzIfLmJsb2NrbmluamEudjEuQnV0dG9uVHlwZUNvbmZpZxI3CgZjdXN0b20YByADKAsyJy5ibG9ja25pbmphLnYxLlRoZW1lQnV0dG9ucy5DdXN0b21FbnRyeRpOCgtDdXN0b21FbnRyeRILCgNrZXkYASABKAkSLgoFdmFsdWUYAiABKAsyHy5ibG9ja25pbmphLnYxLkJ1dHRvblR5cGVDb25maWc6AjgBIs8BChJJbnB1dFZhcmlhbnRDb25maWcSEAoIYmdfY29sb3IYASABKAkSFAoMYm9yZGVyX2NvbG9yGAIgASgJEhQKDGJvcmRlcl93aWR0aBgDIAEoCRISCgp0ZXh0X2NvbG9yGAQgASgJEhIKCmZvY3VzX3JpbmcYBSABKAkSEAoIZm9jdXNfYmcYBiABKAkSFAoMZm9jdXNfYm9yZGVyGAcgASgJEhAKCGhvdmVyX2JnGAggASgJEhkKEXBsYWNlaG9sZGVyX2NvbG9yGAkgASgJItsBCgtUaGVtZUlucHV0cxIyCgdkZWZhdWx0GAEgASgLMiEuYmxvY2tuaW5qYS52MS5JbnB1dFZhcmlhbnRDb25maWcSMQoGZmlsbGVkGAIgASgLMiEuYmxvY2tuaW5qYS52MS5JbnB1dFZhcmlhbnRDb25maWcSMwoIb3V0bGluZWQYAyABKAsyIS5ibG9ja25pbmphLnYxLklucHV0VmFyaWFudENvbmZpZxIwCgVnaG9zdBgEIAEoCzIhLmJsb2NrbmluamEudjEuSW5wdXRWYXJpYW50Q29uZmlnIrUBCgpUaGVtZUNhcmRzEhQKDGJvcmRlcl93aWR0aBgBIAEoCRIUCgxib3JkZXJfc3R5bGUYAiABKAkSFAoMYm9yZGVyX2NvbG9yGAMgASgJEg4KBnNoYWRvdxgEIAEoCRIUCgxzaGFkb3dfY29sb3IYBSABKAkSEgoKYmdfb3BhY2l0eRgGIAEoCRIVCg1iYWNrZHJvcF9ibHVyGAcgASgJEhQKDGhvdmVyX2VmZmVjdBgIIAEoCSLvAwoFVGhlbWUSMAoMbGlnaHRfY29sb3JzGAEgASgLMhouYmxvY2tuaW5qYS52MS5Db2xvclNjaGVtZRIvCgtkYXJrX2NvbG9ycxgCIAEoCzIaLmJsb2NrbmluamEudjEuQ29sb3JTY2hlbWUSMgoKdHlwb2dyYXBoeRgDIAEoCzIeLmJsb2NrbmluamEudjEuVGhlbWVUeXBvZ3JhcGh5EiwKB3NwYWNpbmcYBCABKAsyGy5ibG9ja25pbmphLnYxLlRoZW1lU3BhY2luZxIsCgdlZmZlY3RzGAUgASgLMhsuYmxvY2tuaW5qYS52MS5UaGVtZUVmZmVjdHMSDAoEbW9kZRgGIAEoCRIuCgp1cGRhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIsCgdidXR0b25zGAggASgLMhsuYmxvY2tuaW5qYS52MS5UaGVtZUJ1dHRvbnMSMQoNY3VzdG9tX2NvbG9ycxgJIAMoCzIaLmJsb2NrbmluamEudjEuQ3VzdG9tQ29sb3ISKgoGaW5wdXRzGAogASgLMhouYmxvY2tuaW5qYS52MS5UaGVtZUlucHV0cxIoCgVjYXJkcxgLIAEoCzIZLmJsb2NrbmluamEudjEuVGhlbWVDYXJkcyJxCgtUaGVtZVByZXNldBIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEiMKBXRoZW1lGAQgASgLMhQuYmxvY2tuaW5qYS52MS5UaGVtZRIOCgZzb3VyY2UYBSABKAki2QEKD1VzZXJUaGVtZVByZXNldBIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEiMKBXRoZW1lGAQgASgLMhQuYmxvY2tuaW5qYS52MS5UaGVtZRISCgppc19kZWZhdWx0GAUgASgIEi4KCmNyZWF0ZWRfYXQYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIokCCgRGb250EgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSDgoGZmFtaWx5GAMgASgJEg4KBnNvdXJjZRgEIAEoCRIXCg9zb3VyY2VfdGVtcGxhdGUYBSABKAkSEAoIdmFyaWFudHMYBiADKAkSLQoFZmlsZXMYByADKAsyHi5ibG9ja25pbmphLnYxLkZvbnQuRmlsZXNFbnRyeRIPCgdjc3NfdXJsGAggASgJEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wGiwKCkZpbGVzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJECg5Hb29nbGVGb250SW5mbxIOCgZmYW1pbHkYASABKAkSEAoIdmFyaWFudHMYAiADKAkSEAoIY2F0ZWdvcnkYAyABKAkiOgoLRm9udFZhcmlhbnQSDgoGd2VpZ2h0GAEgASgJEg0KBXN0eWxlGAIgASgJEgwKBGZpbGUYAyABKAkiEQoPR2V0VGhlbWVSZXF1ZXN0IjcKEEdldFRoZW1lUmVzcG9uc2USIwoFdGhlbWUYASABKAsyFC5ibG9ja25pbmphLnYxLlRoZW1lIjkKElVwZGF0ZVRoZW1lUmVxdWVzdBIjCgV0aGVtZRgBIAEoCzIULmJsb2NrbmluamEudjEuVGhlbWUiRwoTVXBkYXRlVGhlbWVSZXNwb25zZRIjCgV0aGVtZRgBIAEoCzIULmJsb2NrbmluamEudjEuVGhlbWUSCwoDY3NzGAIgASgJIhMKEVJlc2V0VGhlbWVSZXF1ZXN0IjkKElJlc2V0VGhlbWVSZXNwb25zZRIjCgV0aGVtZRgBIAEoCzIULmJsb2NrbmluamEudjEuVGhlbWUiPgoXR2VuZXJhdGVUaGVtZUNTU1JlcXVlc3QSIwoFdGhlbWUYASABKAsyFC5ibG9ja25pbmphLnYxLlRoZW1lIicKGEdlbmVyYXRlVGhlbWVDU1NSZXNwb25zZRILCgNjc3MYASABKAkiLwoXTGlzdFRoZW1lUHJlc2V0c1JlcXVlc3QSFAoMdGVtcGxhdGVfa2V5GAEgASgJIl0KGExpc3RUaGVtZVByZXNldHNSZXNwb25zZRIUCgx0ZW1wbGF0ZV9rZXkYASABKAkSKwoHcHJlc2V0cxgCIAMoCzIaLmJsb2NrbmluamEudjEuVGhlbWVQcmVzZXQiQgoXQXBwbHlUaGVtZVByZXNldFJlcXVlc3QSEQoJcHJlc2V0X2lkGAEgASgJEhQKDHRlbXBsYXRlX2tleRgCIAEoCSI/ChhBcHBseVRoZW1lUHJlc2V0UmVzcG9uc2USIwoFdGhlbWUYASABKAsyFC5ibG9ja25pbmphLnYxLlRoZW1lIh0KG0xpc3RVc2VyVGhlbWVQcmVzZXRzUmVxdWVzdCJPChxMaXN0VXNlclRoZW1lUHJlc2V0c1Jlc3BvbnNlEi8KB3ByZXNldHMYASADKAsyHi5ibG9ja25pbmphLnYxLlVzZXJUaGVtZVByZXNldCJ6ChxDcmVhdGVVc2VyVGhlbWVQcmVzZXRSZXF1ZXN0EgwKBG5hbWUYASABKAkSEwoLZGVzY3JpcHRpb24YAiABKAkSIwoFdGhlbWUYAyABKAsyFC5ibG9ja25pbmphLnYxLlRoZW1lEhIKCmlzX2RlZmF1bHQYBCABKAgiTwodQ3JlYXRlVXNlclRoZW1lUHJlc2V0UmVzcG9uc2USLgoGcHJlc2V0GAEgASgLMh4uYmxvY2tuaW5qYS52MS5Vc2VyVGhlbWVQcmVzZXQihgEKHFVwZGF0ZVVzZXJUaGVtZVByZXNldFJlcXVlc3QSCgoCaWQYASABKAkSDAoEbmFtZRgCIAEoCRITCgtkZXNjcmlwdGlvbhgDIAEoCRIjCgV0aGVtZRgEIAEoCzIULmJsb2NrbmluamEudjEuVGhlbWUSEgoKaXNfZGVmYXVsdBgFIAEoCCJPCh1VcGRhdGVVc2VyVGhlbWVQcmVzZXRSZXNwb25zZRIuCgZwcmVzZXQYASABKAsyHi5ibG9ja25pbmphLnYxLlVzZXJUaGVtZVByZXNldCIqChxEZWxldGVVc2VyVGhlbWVQcmVzZXRSZXF1ZXN0EgoKAmlkGAEgASgJIh8KHURlbGV0ZVVzZXJUaGVtZVByZXNldFJlc3BvbnNlIiIKEExpc3RGb250c1JlcXVlc3QSDgoGc291cmNlGAEgASgJIjcKEUxpc3RGb250c1Jlc3BvbnNlEiIKBWZvbnRzGAEgAygLMhMuYmxvY2tuaW5qYS52MS5Gb250IhwKDkdldEZvbnRSZXF1ZXN0EgoKAmlkGAEgASgJIjQKD0dldEZvbnRSZXNwb25zZRIhCgRmb250GAEgASgLMhMuYmxvY2tuaW5qYS52MS5Gb250IjoKFkxpc3RHb29nbGVGb250c1JlcXVlc3QSEAoIY2F0ZWdvcnkYASABKAkSDgoGc2VhcmNoGAIgASgJIkcKF0xpc3RHb29nbGVGb250c1Jlc3BvbnNlEiwKBWZvbnRzGAEgAygLMh0uYmxvY2tuaW5qYS52MS5Hb29nbGVGb250SW5mbyI4ChRBZGRHb29nbGVGb250UmVxdWVzdBIOCgZmYW1pbHkYASABKAkSEAoIdmFyaWFudHMYAiADKAkiOgoVQWRkR29vZ2xlRm9udFJlc3BvbnNlEiEKBGZvbnQYASABKAsyEy5ibG9ja25pbmphLnYxLkZvbnQiHwoRRGVsZXRlRm9udFJlcXVlc3QSCgoCaWQYASABKAkiFAoSRGVsZXRlRm9udFJlc3BvbnNlIjAKF0dldENvbG9yU3dhdGNoZXNSZXF1ZXN0EhUKDW1vZGVfb3ZlcnJpZGUYASABKAkimwEKC0NvbG9yU3dhdGNoEgwKBG5hbWUYASABKAkSDQoFbGFiZWwYAiABKAkSCwoDaHNsGAMgASgJEhEKCWlzX2N1c3RvbRgEIAEoCBITCgtkZXNjcmlwdGlvbhgFIAEoCRIRCglsaWdodF9oc2wYBiABKAkSEAoIZGFya19oc2wYByABKAkSFQoNY29udHJhc3Rfd2l0aBgIIAEoCSKWAQoYR2V0Q29sb3JTd2F0Y2hlc1Jlc3BvbnNlEhUKDXJlc29sdmVkX21vZGUYASABKAkSMAoMdGhlbWVfY29sb3JzGAIgAygLMhouYmxvY2tuaW5qYS52MS5Db2xvclN3YXRjaBIxCg1jdXN0b21fY29sb3JzGAMgAygLMhouYmxvY2tuaW5qYS52MS5Db2xvclN3YXRjaDLlCAoMVGhlbWVTZXJ2aWNlEksKCEdldFRoZW1lEh4uYmxvY2tuaW5qYS52MS5HZXRUaGVtZVJlcXVlc3QaHy5ibG9ja25pbmphLnYxLkdldFRoZW1lUmVzcG9uc2USVAoLVXBkYXRlVGhlbWUSIS5ibG9ja25pbmphLnYxLlVwZGF0ZVRoZW1lUmVxdWVzdBoiLmJsb2NrbmluamEudjEuVXBkYXRlVGhlbWVSZXNwb25zZRJRCgpSZXNldFRoZW1lEiAuYmxvY2tuaW5qYS52MS5SZXNldFRoZW1lUmVxdWVzdBohLmJsb2NrbmluamEudjEuUmVzZXRUaGVtZVJlc3BvbnNlEmMKEEdlbmVyYXRlVGhlbWVDU1MSJi5ibG9ja25pbmphLnYxLkdlbmVyYXRlVGhlbWVDU1NSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5HZW5lcmF0ZVRoZW1lQ1NTUmVzcG9uc2USYwoQR2V0Q29sb3JTd2F0Y2hlcxImLmJsb2NrbmluamEudjEuR2V0Q29sb3JTd2F0Y2hlc1JlcXVlc3QaJy5ibG9ja25pbmphLnYxLkdldENvbG9yU3dhdGNoZXNSZXNwb25zZRJjChBMaXN0VGhlbWVQcmVzZXRzEiYuYmxvY2tuaW5qYS52MS5MaXN0VGhlbWVQcmVzZXRzUmVxdWVzdBonLmJsb2NrbmluamEudjEuTGlzdFRoZW1lUHJlc2V0c1Jlc3BvbnNlEmMKEEFwcGx5VGhlbWVQcmVzZXQSJi5ibG9ja25pbmphLnYxLkFwcGx5VGhlbWVQcmVzZXRSZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5BcHBseVRoZW1lUHJlc2V0UmVzcG9uc2USbwoUTGlzdFVzZXJUaGVtZVByZXNldHMSKi5ibG9ja25pbmphLnYxLkxpc3RVc2VyVGhlbWVQcmVzZXRzUmVxdWVzdBorLmJsb2NrbmluamEudjEuTGlzdFVzZXJUaGVtZVByZXNldHNSZXNwb25zZRJyChVDcmVhdGVVc2VyVGhlbWVQcmVzZXQSKy5ibG9ja25pbmphLnYxLkNyZWF0ZVVzZXJUaGVtZVByZXNldFJlcXVlc3QaLC5ibG9ja25pbmphLnYxLkNyZWF0ZVVzZXJUaGVtZVByZXNldFJlc3BvbnNlEnIKFVVwZGF0ZVVzZXJUaGVtZVByZXNldBIrLmJsb2NrbmluamEudjEuVXBkYXRlVXNlclRoZW1lUHJlc2V0UmVxdWVzdBosLmJsb2NrbmluamEudjEuVXBkYXRlVXNlclRoZW1lUHJlc2V0UmVzcG9uc2UScgoVRGVsZXRlVXNlclRoZW1lUHJlc2V0EisuYmxvY2tuaW5qYS52MS5EZWxldGVVc2VyVGhlbWVQcmVzZXRSZXF1ZXN0GiwuYmxvY2tuaW5qYS52MS5EZWxldGVVc2VyVGhlbWVQcmVzZXRSZXNwb25zZTK4AwoLRm9udFNlcnZpY2USTgoJTGlzdEZvbnRzEh8uYmxvY2tuaW5qYS52MS5MaXN0Rm9udHNSZXF1ZXN0GiAuYmxvY2tuaW5qYS52MS5MaXN0Rm9udHNSZXNwb25zZRJICgdHZXRGb250Eh0uYmxvY2tuaW5qYS52MS5HZXRGb250UmVxdWVzdBoeLmJsb2NrbmluamEudjEuR2V0Rm9udFJlc3BvbnNlEmAKD0xpc3RHb29nbGVGb250cxIlLmJsb2NrbmluamEudjEuTGlzdEdvb2dsZUZvbnRzUmVxdWVzdBomLmJsb2NrbmluamEudjEuTGlzdEdvb2dsZUZvbnRzUmVzcG9uc2USWgoNQWRkR29vZ2xlRm9udBIjLmJsb2NrbmluamEudjEuQWRkR29vZ2xlRm9udFJlcXVlc3QaJC5ibG9ja25pbmphLnYxLkFkZEdvb2dsZUZvbnRSZXNwb25zZRJRCgpEZWxldGVGb250EiAuYmxvY2tuaW5qYS52MS5EZWxldGVGb250UmVxdWVzdBohLmJsb2NrbmluamEudjEuRGVsZXRlRm9udFJlc3BvbnNlQsABChFjb20uYmxvY2tuaW5qYS52MUIKVGhlbWVQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_google_protobuf_timestamp], +); +/** + * ThemeService manages site-wide theme settings + * + * @generated from service blockninja.v1.ThemeService + */ +const ThemeService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_theme, 0); +/** + * FontService manages fonts (Google, uploaded, template-bundled) + * + * @generated from service blockninja.v1.FontService + */ +const FontService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_theme, 1); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/theme.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * List all installed fonts + * + * @generated from rpc blockninja.v1.FontService.ListFonts + */ +FontService.method.listFonts; +/** + * Get a single font by ID + * + * @generated from rpc blockninja.v1.FontService.GetFont + */ +FontService.method.getFont; +/** + * List curated Google Fonts (no API key needed) + * + * @generated from rpc blockninja.v1.FontService.ListGoogleFonts + */ +FontService.method.listGoogleFonts; +/** + * Add a Google Font to the library + * + * @generated from rpc blockninja.v1.FontService.AddGoogleFont + */ +FontService.method.addGoogleFont; +/** + * Delete a font (only user-uploaded fonts can be deleted) + * + * @generated from rpc blockninja.v1.FontService.DeleteFont + */ +FontService.method.deleteFont; + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/theme.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Get current theme settings + * + * @generated from rpc blockninja.v1.ThemeService.GetTheme + */ +ThemeService.method.getTheme; +/** + * Update theme settings + * + * @generated from rpc blockninja.v1.ThemeService.UpdateTheme + */ +ThemeService.method.updateTheme; +/** + * Reset theme to defaults + * + * @generated from rpc blockninja.v1.ThemeService.ResetTheme + */ +ThemeService.method.resetTheme; +/** + * Generate CSS from theme settings (for preview) + * + * @generated from rpc blockninja.v1.ThemeService.GenerateThemeCSS + */ +ThemeService.method.generateThemeCSS; +/** + * Get pre-resolved color swatches for the color picker + * Resolves the correct colors based on site theme mode (not browser preference) + * + * @generated from rpc blockninja.v1.ThemeService.GetColorSwatches + */ +ThemeService.method.getColorSwatches; +/** + * List theme presets for the active template + * + * @generated from rpc blockninja.v1.ThemeService.ListThemePresets + */ +ThemeService.method.listThemePresets; +/** + * Apply a theme preset + * + * @generated from rpc blockninja.v1.ThemeService.ApplyThemePreset + */ +ThemeService.method.applyThemePreset; +/** + * List user-created theme presets (stored in database) + * + * @generated from rpc blockninja.v1.ThemeService.ListUserThemePresets + */ +ThemeService.method.listUserThemePresets; +/** + * Create a user theme preset from current settings + * + * @generated from rpc blockninja.v1.ThemeService.CreateUserThemePreset + */ +ThemeService.method.createUserThemePreset; +/** + * Update a user theme preset + * + * @generated from rpc blockninja.v1.ThemeService.UpdateUserThemePreset + */ +ThemeService.method.updateUserThemePreset; +/** + * Delete a user theme preset + * + * @generated from rpc blockninja.v1.ThemeService.DeleteUserThemePreset + */ +ThemeService.method.deleteUserThemePreset; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/user_engagement.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/user_engagement.proto. + */ +const file_blockninja_v1_user_engagement = /*@__PURE__*/ fileDesc( + "CiNibG9ja25pbmphL3YxL3VzZXJfZW5nYWdlbWVudC5wcm90bxINYmxvY2tuaW5qYS52MSKNAQoJVXNlclZpc2l0EgoKAmlkGAEgASgJEhAKCHRhYmxlX2lkGAIgASgJEg4KBnJvd19pZBgDIAEoCRIVCghwaG90b19pZBgEIAEoCUgAiAEBEi4KCnZpc2l0ZWRfYXQYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgsKCV9waG90b19pZCJaChJNYXJrVmlzaXRlZFJlcXVlc3QSEAoIdGFibGVfaWQYASABKAkSDgoGcm93X2lkGAIgASgJEhUKCHBob3RvX2lkGAMgASgJSACIAQFCCwoJX3Bob3RvX2lkIj4KE01hcmtWaXNpdGVkUmVzcG9uc2USJwoFdmlzaXQYASABKAsyGC5ibG9ja25pbmphLnYxLlVzZXJWaXNpdCI2ChJSZW1vdmVWaXNpdFJlcXVlc3QSEAoIdGFibGVfaWQYASABKAkSDgoGcm93X2lkGAIgASgJIhUKE1JlbW92ZVZpc2l0UmVzcG9uc2UiZgoRTGlzdFZpc2l0c1JlcXVlc3QSLQoKcGFnaW5hdGlvbhgBIAEoCzIZLmJsb2NrbmluamEudjEuUGFnaW5hdGlvbhIVCgh0YWJsZV9pZBgCIAEoCUgAiAEBQgsKCV90YWJsZV9pZCJ1ChJMaXN0VmlzaXRzUmVzcG9uc2USKAoGdmlzaXRzGAEgAygLMhguYmxvY2tuaW5qYS52MS5Vc2VyVmlzaXQSNQoKcGFnaW5hdGlvbhgCIAEoCzIhLmJsb2NrbmluamEudjEuUGFnaW5hdGlvblJlc3BvbnNlImwKDFdpc2hsaXN0SXRlbRIKCgJpZBgBIAEoCRIQCgh0YWJsZV9pZBgCIAEoCRIOCgZyb3dfaWQYAyABKAkSLgoKY3JlYXRlZF9hdBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiOAoUQWRkVG9XaXNobGlzdFJlcXVlc3QSEAoIdGFibGVfaWQYASABKAkSDgoGcm93X2lkGAIgASgJIkIKFUFkZFRvV2lzaGxpc3RSZXNwb25zZRIpCgRpdGVtGAEgASgLMhsuYmxvY2tuaW5qYS52MS5XaXNobGlzdEl0ZW0iPQoZUmVtb3ZlRnJvbVdpc2hsaXN0UmVxdWVzdBIQCgh0YWJsZV9pZBgBIAEoCRIOCgZyb3dfaWQYAiABKAkiHAoaUmVtb3ZlRnJvbVdpc2hsaXN0UmVzcG9uc2UiaAoTTGlzdFdpc2hsaXN0UmVxdWVzdBItCgpwYWdpbmF0aW9uGAEgASgLMhkuYmxvY2tuaW5qYS52MS5QYWdpbmF0aW9uEhUKCHRhYmxlX2lkGAIgASgJSACIAQFCCwoJX3RhYmxlX2lkInkKFExpc3RXaXNobGlzdFJlc3BvbnNlEioKBWl0ZW1zGAEgAygLMhsuYmxvY2tuaW5qYS52MS5XaXNobGlzdEl0ZW0SNQoKcGFnaW5hdGlvbhgCIAEoCzIhLmJsb2NrbmluamEudjEuUGFnaW5hdGlvblJlc3BvbnNlIj4KGkdldEVuZ2FnZW1lbnRTdGF0dXNSZXF1ZXN0EhAKCHRhYmxlX2lkGAEgASgJEg4KBnJvd19pZBgCIAEoCSKQAQobR2V0RW5nYWdlbWVudFN0YXR1c1Jlc3BvbnNlEg8KB3Zpc2l0ZWQYASABKAgSEgoKd2lzaGxpc3RlZBgCIAEoCBI4Cg9sYXN0X3Zpc2l0ZWRfYXQYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSACIAQFCEgoQX2xhc3RfdmlzaXRlZF9hdCJDCh5CdWxrR2V0RW5nYWdlbWVudFN0YXR1c1JlcXVlc3QSEAoIdGFibGVfaWQYASABKAkSDwoHcm93X2lkcxgCIAMoCSLOAQofQnVsa0dldEVuZ2FnZW1lbnRTdGF0dXNSZXNwb25zZRJOCghzdGF0dXNlcxgBIAMoCzI8LmJsb2NrbmluamEudjEuQnVsa0dldEVuZ2FnZW1lbnRTdGF0dXNSZXNwb25zZS5TdGF0dXNlc0VudHJ5GlsKDVN0YXR1c2VzRW50cnkSCwoDa2V5GAEgASgJEjkKBXZhbHVlGAIgASgLMiouYmxvY2tuaW5qYS52MS5HZXRFbmdhZ2VtZW50U3RhdHVzUmVzcG9uc2U6AjgBMp4GChVVc2VyRW5nYWdlbWVudFNlcnZpY2USVAoLTWFya1Zpc2l0ZWQSIS5ibG9ja25pbmphLnYxLk1hcmtWaXNpdGVkUmVxdWVzdBoiLmJsb2NrbmluamEudjEuTWFya1Zpc2l0ZWRSZXNwb25zZRJUCgtSZW1vdmVWaXNpdBIhLmJsb2NrbmluamEudjEuUmVtb3ZlVmlzaXRSZXF1ZXN0GiIuYmxvY2tuaW5qYS52MS5SZW1vdmVWaXNpdFJlc3BvbnNlElEKCkxpc3RWaXNpdHMSIC5ibG9ja25pbmphLnYxLkxpc3RWaXNpdHNSZXF1ZXN0GiEuYmxvY2tuaW5qYS52MS5MaXN0VmlzaXRzUmVzcG9uc2USWgoNQWRkVG9XaXNobGlzdBIjLmJsb2NrbmluamEudjEuQWRkVG9XaXNobGlzdFJlcXVlc3QaJC5ibG9ja25pbmphLnYxLkFkZFRvV2lzaGxpc3RSZXNwb25zZRJpChJSZW1vdmVGcm9tV2lzaGxpc3QSKC5ibG9ja25pbmphLnYxLlJlbW92ZUZyb21XaXNobGlzdFJlcXVlc3QaKS5ibG9ja25pbmphLnYxLlJlbW92ZUZyb21XaXNobGlzdFJlc3BvbnNlElcKDExpc3RXaXNobGlzdBIiLmJsb2NrbmluamEudjEuTGlzdFdpc2hsaXN0UmVxdWVzdBojLmJsb2NrbmluamEudjEuTGlzdFdpc2hsaXN0UmVzcG9uc2USbAoTR2V0RW5nYWdlbWVudFN0YXR1cxIpLmJsb2NrbmluamEudjEuR2V0RW5nYWdlbWVudFN0YXR1c1JlcXVlc3QaKi5ibG9ja25pbmphLnYxLkdldEVuZ2FnZW1lbnRTdGF0dXNSZXNwb25zZRJ4ChdCdWxrR2V0RW5nYWdlbWVudFN0YXR1cxItLmJsb2NrbmluamEudjEuQnVsa0dldEVuZ2FnZW1lbnRTdGF0dXNSZXF1ZXN0Gi4uYmxvY2tuaW5qYS52MS5CdWxrR2V0RW5nYWdlbWVudFN0YXR1c1Jlc3BvbnNlQskBChFjb20uYmxvY2tuaW5qYS52MUITVXNlckVuZ2FnZW1lbnRQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_blockninja_v1_common, file_google_protobuf_timestamp], +); +/** + * UserEngagementService manages visit tracking and wishlists for community users + * + * @generated from service blockninja.v1.UserEngagementService + */ +const UserEngagementService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_user_engagement, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/user_engagement.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * @generated from rpc blockninja.v1.UserEngagementService.MarkVisited + */ +UserEngagementService.method.markVisited; +/** + * @generated from rpc blockninja.v1.UserEngagementService.RemoveVisit + */ +UserEngagementService.method.removeVisit; +/** + * @generated from rpc blockninja.v1.UserEngagementService.ListVisits + */ +UserEngagementService.method.listVisits; +/** + * @generated from rpc blockninja.v1.UserEngagementService.AddToWishlist + */ +UserEngagementService.method.addToWishlist; +/** + * @generated from rpc blockninja.v1.UserEngagementService.RemoveFromWishlist + */ +UserEngagementService.method.removeFromWishlist; +/** + * @generated from rpc blockninja.v1.UserEngagementService.ListWishlist + */ +UserEngagementService.method.listWishlist; +/** + * @generated from rpc blockninja.v1.UserEngagementService.GetEngagementStatus + */ +UserEngagementService.method.getEngagementStatus; +/** + * @generated from rpc blockninja.v1.UserEngagementService.BulkGetEngagementStatus + */ +UserEngagementService.method.bulkGetEngagementStatus; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/video.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/video.proto. + */ +const file_blockninja_v1_video = /*@__PURE__*/ fileDesc( + "ChlibG9ja25pbmphL3YxL3ZpZGVvLnByb3RvEg1ibG9ja25pbmphLnYxItYBCglWaWRlb0l0ZW0SCgoCaWQYASABKAkSFQoNcHJvdmlkZXJfdHlwZRgCIAEoCRITCgtleHRlcm5hbF9pZBgDIAEoCRIRCgllbWJlZF91cmwYBCABKAkSDQoFdGl0bGUYBSABKAkSGAoQZHVyYXRpb25fc2Vjb25kcxgGIAEoBRIVCg10aHVtYm5haWxfdXJsGAcgASgJEg4KBnN0YXR1cxgIIAEoCRIuCgpjcmVhdGVkX2F0GAkgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKjAQoRVmlkZW9Qcm9ncmVzc0l0ZW0SEAoIdmlkZW9faWQYASABKAkSFwoPd2F0Y2hlZF9zZWNvbmRzGAIgASgFEhsKE21heF93YXRjaGVkX3NlY29uZHMYAyABKAUSEQoJY29tcGxldGVkGAQgASgIEjMKD2xhc3Rfd2F0Y2hlZF9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiQgoRTGlzdFZpZGVvc1JlcXVlc3QSLQoKcGFnaW5hdGlvbhgBIAEoCzIZLmJsb2NrbmluamEudjEuUGFnaW5hdGlvbiJTChJMaXN0VmlkZW9zUmVzcG9uc2USKAoGdmlkZW9zGAEgAygLMhguYmxvY2tuaW5qYS52MS5WaWRlb0l0ZW0SEwoLdG90YWxfY291bnQYAiABKAMiHQoPR2V0VmlkZW9SZXF1ZXN0EgoKAmlkGAEgASgJIjsKEEdldFZpZGVvUmVzcG9uc2USJwoFdmlkZW8YASABKAsyGC5ibG9ja25pbmphLnYxLlZpZGVvSXRlbSImChdDcmVhdGVFbWJlZFZpZGVvUmVxdWVzdBILCgN1cmwYASABKAkiQwoYQ3JlYXRlRW1iZWRWaWRlb1Jlc3BvbnNlEicKBXZpZGVvGAEgASgLMhguYmxvY2tuaW5qYS52MS5WaWRlb0l0ZW0iIAoSRGVsZXRlVmlkZW9SZXF1ZXN0EgoKAmlkGAEgASgJIhUKE0RlbGV0ZVZpZGVvUmVzcG9uc2UiQgoVVXBkYXRlUHJvZ3Jlc3NSZXF1ZXN0EhAKCHZpZGVvX2lkGAEgASgJEhcKD2N1cnJlbnRfc2Vjb25kcxgCIAEoBSJMChZVcGRhdGVQcm9ncmVzc1Jlc3BvbnNlEjIKCHByb2dyZXNzGAEgASgLMiAuYmxvY2tuaW5qYS52MS5WaWRlb1Byb2dyZXNzSXRlbSImChJHZXRQcm9ncmVzc1JlcXVlc3QSEAoIdmlkZW9faWQYASABKAkiWwoTR2V0UHJvZ3Jlc3NSZXNwb25zZRI3Cghwcm9ncmVzcxgBIAEoCzIgLmJsb2NrbmluamEudjEuVmlkZW9Qcm9ncmVzc0l0ZW1IAIgBAUILCglfcHJvZ3Jlc3MiFwoVTGlzdE15UHJvZ3Jlc3NSZXF1ZXN0IkwKFkxpc3RNeVByb2dyZXNzUmVzcG9uc2USMgoIcHJvZ3Jlc3MYASADKAsyIC5ibG9ja25pbmphLnYxLlZpZGVvUHJvZ3Jlc3NJdGVtMv0ECgxWaWRlb1NlcnZpY2USUQoKTGlzdFZpZGVvcxIgLmJsb2NrbmluamEudjEuTGlzdFZpZGVvc1JlcXVlc3QaIS5ibG9ja25pbmphLnYxLkxpc3RWaWRlb3NSZXNwb25zZRJLCghHZXRWaWRlbxIeLmJsb2NrbmluamEudjEuR2V0VmlkZW9SZXF1ZXN0Gh8uYmxvY2tuaW5qYS52MS5HZXRWaWRlb1Jlc3BvbnNlEmMKEENyZWF0ZUVtYmVkVmlkZW8SJi5ibG9ja25pbmphLnYxLkNyZWF0ZUVtYmVkVmlkZW9SZXF1ZXN0GicuYmxvY2tuaW5qYS52MS5DcmVhdGVFbWJlZFZpZGVvUmVzcG9uc2USVAoLRGVsZXRlVmlkZW8SIS5ibG9ja25pbmphLnYxLkRlbGV0ZVZpZGVvUmVxdWVzdBoiLmJsb2NrbmluamEudjEuRGVsZXRlVmlkZW9SZXNwb25zZRJdCg5VcGRhdGVQcm9ncmVzcxIkLmJsb2NrbmluamEudjEuVXBkYXRlUHJvZ3Jlc3NSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5VcGRhdGVQcm9ncmVzc1Jlc3BvbnNlElQKC0dldFByb2dyZXNzEiEuYmxvY2tuaW5qYS52MS5HZXRQcm9ncmVzc1JlcXVlc3QaIi5ibG9ja25pbmphLnYxLkdldFByb2dyZXNzUmVzcG9uc2USXQoOTGlzdE15UHJvZ3Jlc3MSJC5ibG9ja25pbmphLnYxLkxpc3RNeVByb2dyZXNzUmVxdWVzdBolLmJsb2NrbmluamEudjEuTGlzdE15UHJvZ3Jlc3NSZXNwb25zZULAAQoRY29tLmJsb2NrbmluamEudjFCClZpZGVvUHJvdG9QAVpKZ2l0LmRldi5hbGV4ZHVubW93LmNvbS9ibG9jay9uaW5qYS9pbnRlcm5hbC9hcGkvYmxvY2tuaW5qYS92MTtibG9ja25pbmphdjGiAgNCWFiqAg1CbG9ja25pbmphLlYxygINQmxvY2tuaW5qYVxWMeICGUJsb2NrbmluamFcVjFcR1BCTWV0YWRhdGHqAg5CbG9ja25pbmphOjpWMWIGcHJvdG8z", + [file_google_protobuf_timestamp, file_blockninja_v1_common], +); +/** + * @generated from service blockninja.v1.VideoService + */ +const VideoService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_video, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/video.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * @generated from rpc blockninja.v1.VideoService.ListVideos + */ +VideoService.method.listVideos; +/** + * @generated from rpc blockninja.v1.VideoService.GetVideo + */ +VideoService.method.getVideo; +/** + * @generated from rpc blockninja.v1.VideoService.CreateEmbedVideo + */ +VideoService.method.createEmbedVideo; +/** + * @generated from rpc blockninja.v1.VideoService.DeleteVideo + */ +VideoService.method.deleteVideo; +/** + * @generated from rpc blockninja.v1.VideoService.UpdateProgress + */ +VideoService.method.updateProgress; +/** + * @generated from rpc blockninja.v1.VideoService.GetProgress + */ +VideoService.method.getProgress; +/** + * @generated from rpc blockninja.v1.VideoService.ListMyProgress + */ +VideoService.method.listMyProgress; + +// @generated by protoc-gen-es v2.10.1 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/workflows.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Describes the file blockninja/v1/workflows.proto. + */ +const file_blockninja_v1_workflows = /*@__PURE__*/ fileDesc( + "Ch1ibG9ja25pbmphL3YxL3dvcmtmbG93cy5wcm90bxINYmxvY2tuaW5qYS52MSLOAwoIV29ya2Zsb3cSCgoCaWQYASABKAkSFAoMd29ya2Zsb3dfa2V5GAIgASgJEgwKBG5hbWUYAyABKAkSGAoLZGVzY3JpcHRpb24YBCABKAlIAIgBARIQCgh0YWJsZV9pZBgFIAEoCRISCgp0YWJsZV9uYW1lGAYgASgJEjgKDHRyaWdnZXJfdHlwZRgHIAEoDjIiLmJsb2NrbmluamEudjEuV29ya2Zsb3dUcmlnZ2VyVHlwZRIvCg50cmlnZ2VyX2NvbmZpZxgIIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSDwoHZW5hYmxlZBgJIAEoCBIPCgd2ZXJzaW9uGAogASgFEhIKCnN0ZXBfY291bnQYCyABKAUSGQoRYWN0aXZlX2V4ZWN1dGlvbnMYDCABKAUSLgoKY3JlYXRlZF9hdBgNIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKdXBkYXRlZF9hdBgOIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFwoKY3JlYXRlZF9ieRgPIAEoCUgBiAEBQg4KDF9kZXNjcmlwdGlvbkINCgtfY3JlYXRlZF9ieSJ4ChRMaXN0V29ya2Zsb3dzUmVxdWVzdBIVCgh0YWJsZV9pZBgBIAEoCUgAiAEBEhIKBWxpbWl0GAIgASgFSAGIAQESEwoGb2Zmc2V0GAMgASgFSAKIAQFCCwoJX3RhYmxlX2lkQggKBl9saW1pdEIJCgdfb2Zmc2V0IlIKFUxpc3RXb3JrZmxvd3NSZXNwb25zZRIqCgl3b3JrZmxvd3MYASADKAsyFy5ibG9ja25pbmphLnYxLldvcmtmbG93Eg0KBXRvdGFsGAIgASgFIiAKEkdldFdvcmtmbG93UmVxdWVzdBIKCgJpZBgBIAEoCSJsChNHZXRXb3JrZmxvd1Jlc3BvbnNlEikKCHdvcmtmbG93GAEgASgLMhcuYmxvY2tuaW5qYS52MS5Xb3JrZmxvdxIqCgVzdGVwcxgCIAMoCzIbLmJsb2NrbmluamEudjEuV29ya2Zsb3dTdGVwIpwCChVDcmVhdGVXb3JrZmxvd1JlcXVlc3QSEAoIdGFibGVfaWQYASABKAkSFAoMd29ya2Zsb3dfa2V5GAIgASgJEgwKBG5hbWUYAyABKAkSGAoLZGVzY3JpcHRpb24YBCABKAlIAIgBARI4Cgx0cmlnZ2VyX3R5cGUYBSABKA4yIi5ibG9ja25pbmphLnYxLldvcmtmbG93VHJpZ2dlclR5cGUSNAoOdHJpZ2dlcl9jb25maWcYBiABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0SAGIAQESFAoHZW5hYmxlZBgHIAEoCEgCiAEBQg4KDF9kZXNjcmlwdGlvbkIRCg9fdHJpZ2dlcl9jb25maWdCCgoIX2VuYWJsZWQiQwoWQ3JlYXRlV29ya2Zsb3dSZXNwb25zZRIpCgh3b3JrZmxvdxgBIAEoCzIXLmJsb2NrbmluamEudjEuV29ya2Zsb3cipAIKFVVwZGF0ZVdvcmtmbG93UmVxdWVzdBIKCgJpZBgBIAEoCRIRCgRuYW1lGAIgASgJSACIAQESGAoLZGVzY3JpcHRpb24YAyABKAlIAYgBARI9Cgx0cmlnZ2VyX3R5cGUYBCABKA4yIi5ibG9ja25pbmphLnYxLldvcmtmbG93VHJpZ2dlclR5cGVIAogBARI0Cg50cmlnZ2VyX2NvbmZpZxgFIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3RIA4gBARIUCgdlbmFibGVkGAYgASgISASIAQFCBwoFX25hbWVCDgoMX2Rlc2NyaXB0aW9uQg8KDV90cmlnZ2VyX3R5cGVCEQoPX3RyaWdnZXJfY29uZmlnQgoKCF9lbmFibGVkIkMKFlVwZGF0ZVdvcmtmbG93UmVzcG9uc2USKQoId29ya2Zsb3cYASABKAsyFy5ibG9ja25pbmphLnYxLldvcmtmbG93IiMKFURlbGV0ZVdvcmtmbG93UmVxdWVzdBIKCgJpZBgBIAEoCSIYChZEZWxldGVXb3JrZmxvd1Jlc3BvbnNlIsgCCgxXb3JrZmxvd1N0ZXASCgoCaWQYASABKAkSEwoLd29ya2Zsb3dfaWQYAiABKAkSEQoEbmFtZRgDIAEoCUgAiAEBEhIKCnN0ZXBfb3JkZXIYBCABKAUSMgoJc3RlcF90eXBlGAUgASgOMh8uYmxvY2tuaW5qYS52MS5Xb3JrZmxvd1N0ZXBUeXBlEiwKC3N0ZXBfY29uZmlnGAYgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIhChRjb25kaXRpb25fZXhwcmVzc2lvbhgHIAEoCUgBiAEBEhkKEWNvbnRpbnVlX29uX2Vycm9yGAggASgIEi4KCmNyZWF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgcKBV9uYW1lQhcKFV9jb25kaXRpb25fZXhwcmVzc2lvbiIvChhMaXN0V29ya2Zsb3dTdGVwc1JlcXVlc3QSEwoLd29ya2Zsb3dfaWQYASABKAkiRwoZTGlzdFdvcmtmbG93U3RlcHNSZXNwb25zZRIqCgVzdGVwcxgBIAMoCzIbLmJsb2NrbmluamEudjEuV29ya2Zsb3dTdGVwIt0CChlDcmVhdGVXb3JrZmxvd1N0ZXBSZXF1ZXN0EhMKC3dvcmtmbG93X2lkGAEgASgJEhEKBG5hbWUYAiABKAlIAIgBARIyCglzdGVwX3R5cGUYAyABKA4yHy5ibG9ja25pbmphLnYxLldvcmtmbG93U3RlcFR5cGUSMQoLc3RlcF9jb25maWcYBCABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0SAGIAQESIQoUY29uZGl0aW9uX2V4cHJlc3Npb24YBSABKAlIAogBARIeChFjb250aW51ZV9vbl9lcnJvchgGIAEoCEgDiAEBEhcKCnN0ZXBfb3JkZXIYByABKAVIBIgBAUIHCgVfbmFtZUIOCgxfc3RlcF9jb25maWdCFwoVX2NvbmRpdGlvbl9leHByZXNzaW9uQhQKEl9jb250aW51ZV9vbl9lcnJvckINCgtfc3RlcF9vcmRlciJHChpDcmVhdGVXb3JrZmxvd1N0ZXBSZXNwb25zZRIpCgRzdGVwGAEgASgLMhsuYmxvY2tuaW5qYS52MS5Xb3JrZmxvd1N0ZXAivwIKGVVwZGF0ZVdvcmtmbG93U3RlcFJlcXVlc3QSCgoCaWQYASABKAkSEQoEbmFtZRgCIAEoCUgAiAEBEjcKCXN0ZXBfdHlwZRgDIAEoDjIfLmJsb2NrbmluamEudjEuV29ya2Zsb3dTdGVwVHlwZUgBiAEBEjEKC3N0ZXBfY29uZmlnGAQgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdEgCiAEBEiEKFGNvbmRpdGlvbl9leHByZXNzaW9uGAUgASgJSAOIAQESHgoRY29udGludWVfb25fZXJyb3IYBiABKAhIBIgBAUIHCgVfbmFtZUIMCgpfc3RlcF90eXBlQg4KDF9zdGVwX2NvbmZpZ0IXChVfY29uZGl0aW9uX2V4cHJlc3Npb25CFAoSX2NvbnRpbnVlX29uX2Vycm9yIkcKGlVwZGF0ZVdvcmtmbG93U3RlcFJlc3BvbnNlEikKBHN0ZXAYASABKAsyGy5ibG9ja25pbmphLnYxLldvcmtmbG93U3RlcCInChlEZWxldGVXb3JrZmxvd1N0ZXBSZXF1ZXN0EgoKAmlkGAEgASgJIhwKGkRlbGV0ZVdvcmtmbG93U3RlcFJlc3BvbnNlIkQKG1Jlb3JkZXJXb3JrZmxvd1N0ZXBzUmVxdWVzdBITCgt3b3JrZmxvd19pZBgBIAEoCRIQCghzdGVwX2lkcxgCIAMoCSJKChxSZW9yZGVyV29ya2Zsb3dTdGVwc1Jlc3BvbnNlEioKBXN0ZXBzGAEgAygLMhsuYmxvY2tuaW5qYS52MS5Xb3JrZmxvd1N0ZXAi2QMKEVdvcmtmbG93RXhlY3V0aW9uEgoKAmlkGAEgASgJEhMKC3dvcmtmbG93X2lkGAIgASgJEhUKDXdvcmtmbG93X25hbWUYAyABKAkSEAoIdGFibGVfaWQYBCABKAkSDgoGcm93X2lkGAUgASgJEjYKBnN0YXR1cxgGIAEoDjImLmJsb2NrbmluamEudjEuV29ya2Zsb3dFeGVjdXRpb25TdGF0dXMSGgoSY3VycmVudF9zdGVwX29yZGVyGAcgASgFEigKB2NvbnRleHQYCCABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0EhoKDWVycm9yX21lc3NhZ2UYCSABKAlIAIgBARIuCgpzdGFydGVkX2F0GAogASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBI1Cgxjb21wbGV0ZWRfYXQYCyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSAGIAQESNQoMc2NoZWR1bGVkX2F0GAwgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgCiAEBQhAKDl9lcnJvcl9tZXNzYWdlQg8KDV9jb21wbGV0ZWRfYXRCDwoNX3NjaGVkdWxlZF9hdCKRAwoVV29ya2Zsb3dFeGVjdXRpb25TdGVwEgoKAmlkGAEgASgJEhIKCnN0ZXBfb3JkZXIYAiABKAUSMgoJc3RlcF90eXBlGAMgASgOMh8uYmxvY2tuaW5qYS52MS5Xb3JrZmxvd1N0ZXBUeXBlEg4KBnN0YXR1cxgEIAEoCRIrCgVpbnB1dBgFIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3RIAIgBARIsCgZvdXRwdXQYBiABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0SAGIAQESGgoNZXJyb3JfbWVzc2FnZRgHIAEoCUgCiAEBEi4KCnN0YXJ0ZWRfYXQYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjUKDGNvbXBsZXRlZF9hdBgJIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBIA4gBAUIICgZfaW5wdXRCCQoHX291dHB1dEIQCg5fZXJyb3JfbWVzc2FnZUIPCg1fY29tcGxldGVkX2F0Iu8BCh1MaXN0V29ya2Zsb3dFeGVjdXRpb25zUmVxdWVzdBIYCgt3b3JrZmxvd19pZBgBIAEoCUgAiAEBEhMKBnJvd19pZBgCIAEoCUgBiAEBEjsKBnN0YXR1cxgDIAEoDjImLmJsb2NrbmluamEudjEuV29ya2Zsb3dFeGVjdXRpb25TdGF0dXNIAogBARISCgVsaW1pdBgEIAEoBUgDiAEBEhMKBm9mZnNldBgFIAEoBUgEiAEBQg4KDF93b3JrZmxvd19pZEIJCgdfcm93X2lkQgkKB19zdGF0dXNCCAoGX2xpbWl0QgkKB19vZmZzZXQiZQoeTGlzdFdvcmtmbG93RXhlY3V0aW9uc1Jlc3BvbnNlEjQKCmV4ZWN1dGlvbnMYASADKAsyIC5ibG9ja25pbmphLnYxLldvcmtmbG93RXhlY3V0aW9uEg0KBXRvdGFsGAIgASgFIikKG0dldFdvcmtmbG93RXhlY3V0aW9uUmVxdWVzdBIKCgJpZBgBIAEoCSKIAQocR2V0V29ya2Zsb3dFeGVjdXRpb25SZXNwb25zZRIzCglleGVjdXRpb24YASABKAsyIC5ibG9ja25pbmphLnYxLldvcmtmbG93RXhlY3V0aW9uEjMKBXN0ZXBzGAIgAygLMiQuYmxvY2tuaW5qYS52MS5Xb3JrZmxvd0V4ZWN1dGlvblN0ZXAiLAoeQ2FuY2VsV29ya2Zsb3dFeGVjdXRpb25SZXF1ZXN0EgoKAmlkGAEgASgJIlYKH0NhbmNlbFdvcmtmbG93RXhlY3V0aW9uUmVzcG9uc2USMwoJZXhlY3V0aW9uGAEgASgLMiAuYmxvY2tuaW5qYS52MS5Xb3JrZmxvd0V4ZWN1dGlvbiIrCh1SZXRyeVdvcmtmbG93RXhlY3V0aW9uUmVxdWVzdBIKCgJpZBgBIAEoCSJVCh5SZXRyeVdvcmtmbG93RXhlY3V0aW9uUmVzcG9uc2USMwoJZXhlY3V0aW9uGAEgASgLMiAuYmxvY2tuaW5qYS52MS5Xb3JrZmxvd0V4ZWN1dGlvbiJFCh5UcmlnZ2VyV29ya2Zsb3dNYW51YWxseVJlcXVlc3QSEwoLd29ya2Zsb3dfaWQYASABKAkSDgoGcm93X2lkGAIgASgJIlYKH1RyaWdnZXJXb3JrZmxvd01hbnVhbGx5UmVzcG9uc2USMwoJZXhlY3V0aW9uGAEgASgLMiAuYmxvY2tuaW5qYS52MS5Xb3JrZmxvd0V4ZWN1dGlvbiJLChNUZXN0V29ya2Zsb3dSZXF1ZXN0EhMKC3dvcmtmbG93X2lkGAEgASgJEg4KBnJvd19pZBgCIAEoCRIPCgdkcnlfcnVuGAMgASgIInQKFFRlc3RXb3JrZmxvd1Jlc3BvbnNlEjMKBXN0ZXBzGAEgAygLMiQuYmxvY2tuaW5qYS52MS5Xb3JrZmxvd0V4ZWN1dGlvblN0ZXASFQoNd291bGRfc3VjY2VlZBgCIAEoCBIQCgh3YXJuaW5ncxgDIAMoCSriAQoTV29ya2Zsb3dUcmlnZ2VyVHlwZRIgChxXT1JLRkxPV19UUklHR0VSX1VOU1BFQ0lGSUVEEAASIQodV09SS0ZMT1dfVFJJR0dFUl9ST1dfSU5TRVJURUQQARIgChxXT1JLRkxPV19UUklHR0VSX1JPV19VUERBVEVEEAISIAocV09SS0ZMT1dfVFJJR0dFUl9ST1dfREVMRVRFRBADEiIKHldPUktGTE9XX1RSSUdHRVJfRklFTERfQ0hBTkdFRBAEEh4KGldPUktGTE9XX1RSSUdHRVJfU0NIRURVTEVEEAUqwwIKEFdvcmtmbG93U3RlcFR5cGUSHQoZV09SS0ZMT1dfU1RFUF9VTlNQRUNJRklFRBAAEiQKIFdPUktGTE9XX1NURVBfU0VORF9DT01NVU5JQ0FUSU9OEAESHAoYV09SS0ZMT1dfU1RFUF9VUERBVEVfUk9XEAISJQohV09SS0ZMT1dfU1RFUF9DUkVBVEVfVkVSSUZJQ0FUSU9OEAMSGwoXV09SS0ZMT1dfU1RFUF9XQUlUX1RJTUUQBBIjCh9XT1JLRkxPV19TVEVQX1dBSVRfVkVSSUZJQ0FUSU9OEAUSIQodV09SS0ZMT1dfU1RFUF9UUklHR0VSX1dFQkhPT0sQBhIYChRXT1JLRkxPV19TVEVQX0JSQU5DSBAHEiYKIldPUktGTE9XX1NURVBfQ1JFQVRFX1BSRUZJTExfVE9LRU4QCCqxAgoXV29ya2Zsb3dFeGVjdXRpb25TdGF0dXMSKQolV09SS0ZMT1dfRVhFQ1VUSU9OX1NUQVRVU19VTlNQRUNJRklFRBAAEiUKIVdPUktGTE9XX0VYRUNVVElPTl9TVEFUVVNfUEVORElORxABEiUKIVdPUktGTE9XX0VYRUNVVElPTl9TVEFUVVNfUlVOTklORxACEiUKIVdPUktGTE9XX0VYRUNVVElPTl9TVEFUVVNfV0FJVElORxADEicKI1dPUktGTE9XX0VYRUNVVElPTl9TVEFUVVNfQ09NUExFVEVEEAQSJAogV09SS0ZMT1dfRVhFQ1VUSU9OX1NUQVRVU19GQUlMRUQQBRInCiNXT1JLRkxPV19FWEVDVVRJT05fU1RBVFVTX0NBTkNFTExFRBAGKpABChVWZXJpZmljYXRpb25Ub2tlblR5cGUSJwojVkVSSUZJQ0FUSU9OX1RPS0VOX1RZUEVfVU5TUEVDSUZJRUQQABImCiJWRVJJRklDQVRJT05fVE9LRU5fVFlQRV9NQUdJQ19MSU5LEAESJgoiVkVSSUZJQ0FUSU9OX1RPS0VOX1RZUEVfQ09ERV9FTlRSWRACMqcNChBXb3JrZmxvd3NTZXJ2aWNlEloKDUxpc3RXb3JrZmxvd3MSIy5ibG9ja25pbmphLnYxLkxpc3RXb3JrZmxvd3NSZXF1ZXN0GiQuYmxvY2tuaW5qYS52MS5MaXN0V29ya2Zsb3dzUmVzcG9uc2USVAoLR2V0V29ya2Zsb3cSIS5ibG9ja25pbmphLnYxLkdldFdvcmtmbG93UmVxdWVzdBoiLmJsb2NrbmluamEudjEuR2V0V29ya2Zsb3dSZXNwb25zZRJdCg5DcmVhdGVXb3JrZmxvdxIkLmJsb2NrbmluamEudjEuQ3JlYXRlV29ya2Zsb3dSZXF1ZXN0GiUuYmxvY2tuaW5qYS52MS5DcmVhdGVXb3JrZmxvd1Jlc3BvbnNlEl0KDlVwZGF0ZVdvcmtmbG93EiQuYmxvY2tuaW5qYS52MS5VcGRhdGVXb3JrZmxvd1JlcXVlc3QaJS5ibG9ja25pbmphLnYxLlVwZGF0ZVdvcmtmbG93UmVzcG9uc2USXQoORGVsZXRlV29ya2Zsb3cSJC5ibG9ja25pbmphLnYxLkRlbGV0ZVdvcmtmbG93UmVxdWVzdBolLmJsb2NrbmluamEudjEuRGVsZXRlV29ya2Zsb3dSZXNwb25zZRJmChFMaXN0V29ya2Zsb3dTdGVwcxInLmJsb2NrbmluamEudjEuTGlzdFdvcmtmbG93U3RlcHNSZXF1ZXN0GiguYmxvY2tuaW5qYS52MS5MaXN0V29ya2Zsb3dTdGVwc1Jlc3BvbnNlEmkKEkNyZWF0ZVdvcmtmbG93U3RlcBIoLmJsb2NrbmluamEudjEuQ3JlYXRlV29ya2Zsb3dTdGVwUmVxdWVzdBopLmJsb2NrbmluamEudjEuQ3JlYXRlV29ya2Zsb3dTdGVwUmVzcG9uc2USaQoSVXBkYXRlV29ya2Zsb3dTdGVwEiguYmxvY2tuaW5qYS52MS5VcGRhdGVXb3JrZmxvd1N0ZXBSZXF1ZXN0GikuYmxvY2tuaW5qYS52MS5VcGRhdGVXb3JrZmxvd1N0ZXBSZXNwb25zZRJpChJEZWxldGVXb3JrZmxvd1N0ZXASKC5ibG9ja25pbmphLnYxLkRlbGV0ZVdvcmtmbG93U3RlcFJlcXVlc3QaKS5ibG9ja25pbmphLnYxLkRlbGV0ZVdvcmtmbG93U3RlcFJlc3BvbnNlEm8KFFJlb3JkZXJXb3JrZmxvd1N0ZXBzEiouYmxvY2tuaW5qYS52MS5SZW9yZGVyV29ya2Zsb3dTdGVwc1JlcXVlc3QaKy5ibG9ja25pbmphLnYxLlJlb3JkZXJXb3JrZmxvd1N0ZXBzUmVzcG9uc2USdQoWTGlzdFdvcmtmbG93RXhlY3V0aW9ucxIsLmJsb2NrbmluamEudjEuTGlzdFdvcmtmbG93RXhlY3V0aW9uc1JlcXVlc3QaLS5ibG9ja25pbmphLnYxLkxpc3RXb3JrZmxvd0V4ZWN1dGlvbnNSZXNwb25zZRJvChRHZXRXb3JrZmxvd0V4ZWN1dGlvbhIqLmJsb2NrbmluamEudjEuR2V0V29ya2Zsb3dFeGVjdXRpb25SZXF1ZXN0GisuYmxvY2tuaW5qYS52MS5HZXRXb3JrZmxvd0V4ZWN1dGlvblJlc3BvbnNlEngKF0NhbmNlbFdvcmtmbG93RXhlY3V0aW9uEi0uYmxvY2tuaW5qYS52MS5DYW5jZWxXb3JrZmxvd0V4ZWN1dGlvblJlcXVlc3QaLi5ibG9ja25pbmphLnYxLkNhbmNlbFdvcmtmbG93RXhlY3V0aW9uUmVzcG9uc2USdQoWUmV0cnlXb3JrZmxvd0V4ZWN1dGlvbhIsLmJsb2NrbmluamEudjEuUmV0cnlXb3JrZmxvd0V4ZWN1dGlvblJlcXVlc3QaLS5ibG9ja25pbmphLnYxLlJldHJ5V29ya2Zsb3dFeGVjdXRpb25SZXNwb25zZRJ4ChdUcmlnZ2VyV29ya2Zsb3dNYW51YWxseRItLmJsb2NrbmluamEudjEuVHJpZ2dlcldvcmtmbG93TWFudWFsbHlSZXF1ZXN0Gi4uYmxvY2tuaW5qYS52MS5UcmlnZ2VyV29ya2Zsb3dNYW51YWxseVJlc3BvbnNlElcKDFRlc3RXb3JrZmxvdxIiLmJsb2NrbmluamEudjEuVGVzdFdvcmtmbG93UmVxdWVzdBojLmJsb2NrbmluamEudjEuVGVzdFdvcmtmbG93UmVzcG9uc2VCxAEKEWNvbS5ibG9ja25pbmphLnYxQg5Xb3JrZmxvd3NQcm90b1ABWkpnaXQuZGV2LmFsZXhkdW5tb3cuY29tL2Jsb2NrL25pbmphL2ludGVybmFsL2FwaS9ibG9ja25pbmphL3YxO2Jsb2NrbmluamF2MaICA0JYWKoCDUJsb2NrbmluamEuVjHKAg1CbG9ja25pbmphXFYx4gIZQmxvY2tuaW5qYVxWMVxHUEJNZXRhZGF0YeoCDkJsb2NrbmluamE6OlYxYgZwcm90bzM", + [file_google_protobuf_struct, file_google_protobuf_timestamp], +); +/** + * @generated from enum blockninja.v1.WorkflowTriggerType + */ +var WorkflowTriggerType; +(function (WorkflowTriggerType) { + /** + * @generated from enum value: WORKFLOW_TRIGGER_UNSPECIFIED = 0; + */ + WorkflowTriggerType[(WorkflowTriggerType["WORKFLOW_TRIGGER_UNSPECIFIED"] = 0)] = "WORKFLOW_TRIGGER_UNSPECIFIED"; + /** + * @generated from enum value: WORKFLOW_TRIGGER_ROW_INSERTED = 1; + */ + WorkflowTriggerType[(WorkflowTriggerType["WORKFLOW_TRIGGER_ROW_INSERTED"] = 1)] = "WORKFLOW_TRIGGER_ROW_INSERTED"; + /** + * @generated from enum value: WORKFLOW_TRIGGER_ROW_UPDATED = 2; + */ + WorkflowTriggerType[(WorkflowTriggerType["WORKFLOW_TRIGGER_ROW_UPDATED"] = 2)] = "WORKFLOW_TRIGGER_ROW_UPDATED"; + /** + * @generated from enum value: WORKFLOW_TRIGGER_ROW_DELETED = 3; + */ + WorkflowTriggerType[(WorkflowTriggerType["WORKFLOW_TRIGGER_ROW_DELETED"] = 3)] = "WORKFLOW_TRIGGER_ROW_DELETED"; + /** + * @generated from enum value: WORKFLOW_TRIGGER_FIELD_CHANGED = 4; + */ + WorkflowTriggerType[(WorkflowTriggerType["WORKFLOW_TRIGGER_FIELD_CHANGED"] = 4)] = "WORKFLOW_TRIGGER_FIELD_CHANGED"; + /** + * @generated from enum value: WORKFLOW_TRIGGER_SCHEDULED = 5; + */ + WorkflowTriggerType[(WorkflowTriggerType["WORKFLOW_TRIGGER_SCHEDULED"] = 5)] = "WORKFLOW_TRIGGER_SCHEDULED"; +})(WorkflowTriggerType || (WorkflowTriggerType = {})); +/** + * @generated from enum blockninja.v1.WorkflowStepType + */ +var WorkflowStepType; +(function (WorkflowStepType) { + /** + * @generated from enum value: WORKFLOW_STEP_UNSPECIFIED = 0; + */ + WorkflowStepType[(WorkflowStepType["WORKFLOW_STEP_UNSPECIFIED"] = 0)] = "WORKFLOW_STEP_UNSPECIFIED"; + /** + * @generated from enum value: WORKFLOW_STEP_SEND_COMMUNICATION = 1; + */ + WorkflowStepType[(WorkflowStepType["WORKFLOW_STEP_SEND_COMMUNICATION"] = 1)] = "WORKFLOW_STEP_SEND_COMMUNICATION"; + /** + * @generated from enum value: WORKFLOW_STEP_UPDATE_ROW = 2; + */ + WorkflowStepType[(WorkflowStepType["WORKFLOW_STEP_UPDATE_ROW"] = 2)] = "WORKFLOW_STEP_UPDATE_ROW"; + /** + * @generated from enum value: WORKFLOW_STEP_CREATE_VERIFICATION = 3; + */ + WorkflowStepType[(WorkflowStepType["WORKFLOW_STEP_CREATE_VERIFICATION"] = 3)] = "WORKFLOW_STEP_CREATE_VERIFICATION"; + /** + * @generated from enum value: WORKFLOW_STEP_WAIT_TIME = 4; + */ + WorkflowStepType[(WorkflowStepType["WORKFLOW_STEP_WAIT_TIME"] = 4)] = "WORKFLOW_STEP_WAIT_TIME"; + /** + * @generated from enum value: WORKFLOW_STEP_WAIT_VERIFICATION = 5; + */ + WorkflowStepType[(WorkflowStepType["WORKFLOW_STEP_WAIT_VERIFICATION"] = 5)] = "WORKFLOW_STEP_WAIT_VERIFICATION"; + /** + * @generated from enum value: WORKFLOW_STEP_TRIGGER_WEBHOOK = 6; + */ + WorkflowStepType[(WorkflowStepType["WORKFLOW_STEP_TRIGGER_WEBHOOK"] = 6)] = "WORKFLOW_STEP_TRIGGER_WEBHOOK"; + /** + * @generated from enum value: WORKFLOW_STEP_BRANCH = 7; + */ + WorkflowStepType[(WorkflowStepType["WORKFLOW_STEP_BRANCH"] = 7)] = "WORKFLOW_STEP_BRANCH"; + /** + * @generated from enum value: WORKFLOW_STEP_CREATE_PREFILL_TOKEN = 8; + */ + WorkflowStepType[(WorkflowStepType["WORKFLOW_STEP_CREATE_PREFILL_TOKEN"] = 8)] = "WORKFLOW_STEP_CREATE_PREFILL_TOKEN"; +})(WorkflowStepType || (WorkflowStepType = {})); +/** + * @generated from enum blockninja.v1.WorkflowExecutionStatus + */ +var WorkflowExecutionStatus; +(function (WorkflowExecutionStatus) { + /** + * @generated from enum value: WORKFLOW_EXECUTION_STATUS_UNSPECIFIED = 0; + */ + WorkflowExecutionStatus[(WorkflowExecutionStatus["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * @generated from enum value: WORKFLOW_EXECUTION_STATUS_PENDING = 1; + */ + WorkflowExecutionStatus[(WorkflowExecutionStatus["PENDING"] = 1)] = "PENDING"; + /** + * @generated from enum value: WORKFLOW_EXECUTION_STATUS_RUNNING = 2; + */ + WorkflowExecutionStatus[(WorkflowExecutionStatus["RUNNING"] = 2)] = "RUNNING"; + /** + * @generated from enum value: WORKFLOW_EXECUTION_STATUS_WAITING = 3; + */ + WorkflowExecutionStatus[(WorkflowExecutionStatus["WAITING"] = 3)] = "WAITING"; + /** + * @generated from enum value: WORKFLOW_EXECUTION_STATUS_COMPLETED = 4; + */ + WorkflowExecutionStatus[(WorkflowExecutionStatus["COMPLETED"] = 4)] = "COMPLETED"; + /** + * @generated from enum value: WORKFLOW_EXECUTION_STATUS_FAILED = 5; + */ + WorkflowExecutionStatus[(WorkflowExecutionStatus["FAILED"] = 5)] = "FAILED"; + /** + * @generated from enum value: WORKFLOW_EXECUTION_STATUS_CANCELLED = 6; + */ + WorkflowExecutionStatus[(WorkflowExecutionStatus["CANCELLED"] = 6)] = "CANCELLED"; +})(WorkflowExecutionStatus || (WorkflowExecutionStatus = {})); +/** + * @generated from enum blockninja.v1.VerificationTokenType + */ +var VerificationTokenType; +(function (VerificationTokenType) { + /** + * @generated from enum value: VERIFICATION_TOKEN_TYPE_UNSPECIFIED = 0; + */ + VerificationTokenType[(VerificationTokenType["UNSPECIFIED"] = 0)] = "UNSPECIFIED"; + /** + * @generated from enum value: VERIFICATION_TOKEN_TYPE_MAGIC_LINK = 1; + */ + VerificationTokenType[(VerificationTokenType["MAGIC_LINK"] = 1)] = "MAGIC_LINK"; + /** + * @generated from enum value: VERIFICATION_TOKEN_TYPE_CODE_ENTRY = 2; + */ + VerificationTokenType[(VerificationTokenType["CODE_ENTRY"] = 2)] = "CODE_ENTRY"; +})(VerificationTokenType || (VerificationTokenType = {})); +/** + * WorkflowsService manages data table automation workflows + * + * @generated from service blockninja.v1.WorkflowsService + */ +const WorkflowsService = /*@__PURE__*/ serviceDesc(file_blockninja_v1_workflows, 0); + +// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts,import_extension=none" +// @generated from file blockninja/v1/workflows.proto (package blockninja.v1, syntax proto3) +/* eslint-disable */ +/** + * Workflow CRUD + * + * @generated from rpc blockninja.v1.WorkflowsService.ListWorkflows + */ +WorkflowsService.method.listWorkflows; +/** + * @generated from rpc blockninja.v1.WorkflowsService.GetWorkflow + */ +WorkflowsService.method.getWorkflow; +/** + * @generated from rpc blockninja.v1.WorkflowsService.CreateWorkflow + */ +WorkflowsService.method.createWorkflow; +/** + * @generated from rpc blockninja.v1.WorkflowsService.UpdateWorkflow + */ +WorkflowsService.method.updateWorkflow; +/** + * @generated from rpc blockninja.v1.WorkflowsService.DeleteWorkflow + */ +WorkflowsService.method.deleteWorkflow; +/** + * Step management + * + * @generated from rpc blockninja.v1.WorkflowsService.ListWorkflowSteps + */ +WorkflowsService.method.listWorkflowSteps; +/** + * @generated from rpc blockninja.v1.WorkflowsService.CreateWorkflowStep + */ +WorkflowsService.method.createWorkflowStep; +/** + * @generated from rpc blockninja.v1.WorkflowsService.UpdateWorkflowStep + */ +WorkflowsService.method.updateWorkflowStep; +/** + * @generated from rpc blockninja.v1.WorkflowsService.DeleteWorkflowStep + */ +WorkflowsService.method.deleteWorkflowStep; +/** + * @generated from rpc blockninja.v1.WorkflowsService.ReorderWorkflowSteps + */ +WorkflowsService.method.reorderWorkflowSteps; +/** + * Execution management + * + * @generated from rpc blockninja.v1.WorkflowsService.ListWorkflowExecutions + */ +WorkflowsService.method.listWorkflowExecutions; +/** + * @generated from rpc blockninja.v1.WorkflowsService.GetWorkflowExecution + */ +WorkflowsService.method.getWorkflowExecution; +/** + * @generated from rpc blockninja.v1.WorkflowsService.CancelWorkflowExecution + */ +WorkflowsService.method.cancelWorkflowExecution; +/** + * @generated from rpc blockninja.v1.WorkflowsService.RetryWorkflowExecution + */ +WorkflowsService.method.retryWorkflowExecution; +/** + * Manual trigger + * + * @generated from rpc blockninja.v1.WorkflowsService.TriggerWorkflowManually + */ +WorkflowsService.method.triggerWorkflowManually; +/** + * Testing + * + * @generated from rpc blockninja.v1.WorkflowsService.TestWorkflow + */ +WorkflowsService.method.testWorkflow; + +const { useMemo: useMemo$1, useLayoutEffect, useEffect: useEffect$1, useRef: useRef$2, useCallback: useCallback$2 } = await importShared("react"); + +// https://github.com/facebook/react/blob/master/packages/shared/ExecutionEnvironment.js +const canUseDOM = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined"; + +function isWindow(element) { + const elementString = Object.prototype.toString.call(element); + return ( + elementString === "[object Window]" || // In Electron context the Window object serializes to [object global] + elementString === "[object global]" + ); +} + +function isNode$1(node) { + return "nodeType" in node; +} + +function getWindow(target) { + var _target$ownerDocument, _target$ownerDocument2; + + if (!target) { + return window; + } + + if (isWindow(target)) { + return target; + } + + if (!isNode$1(target)) { + return window; + } + + return (_target$ownerDocument = (_target$ownerDocument2 = target.ownerDocument) == null ? void 0 : _target$ownerDocument2.defaultView) != null ? _target$ownerDocument : window; +} + +function isDocument(node) { + const { Document } = getWindow(node); + return node instanceof Document; +} + +function isHTMLElement(node) { + if (isWindow(node)) { + return false; + } + + return node instanceof getWindow(node).HTMLElement; +} + +function isSVGElement(node) { + return node instanceof getWindow(node).SVGElement; +} + +function getOwnerDocument(target) { + if (!target) { + return document; + } + + if (isWindow(target)) { + return target.document; + } + + if (!isNode$1(target)) { + return document; + } + + if (isDocument(target)) { + return target; + } + + if (isHTMLElement(target) || isSVGElement(target)) { + return target.ownerDocument; + } + + return document; +} + +/** + * A hook that resolves to useEffect on the server and useLayoutEffect on the client + * @param callback {function} Callback function that is invoked when the dependencies of the hook change + */ + +const useIsomorphicLayoutEffect = canUseDOM ? useLayoutEffect : useEffect$1; + +function useEvent(handler) { + const handlerRef = useRef$2(handler); + useIsomorphicLayoutEffect(() => { + handlerRef.current = handler; + }); + return useCallback$2(function () { + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + return handlerRef.current == null ? void 0 : handlerRef.current(...args); + }, []); +} + +function useInterval() { + const intervalRef = useRef$2(null); + const set = useCallback$2((listener, duration) => { + intervalRef.current = setInterval(listener, duration); + }, []); + const clear = useCallback$2(() => { + if (intervalRef.current !== null) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + }, []); + return [set, clear]; +} + +function useLatestValue(value, dependencies) { + if (dependencies === void 0) { + dependencies = [value]; + } + + const valueRef = useRef$2(value); + useIsomorphicLayoutEffect(() => { + if (valueRef.current !== value) { + valueRef.current = value; + } + }, dependencies); + return valueRef; +} + +function useLazyMemo(callback, dependencies) { + const valueRef = useRef$2(); + return useMemo$1( + () => { + const newValue = callback(valueRef.current); + valueRef.current = newValue; + return newValue; + }, // eslint-disable-next-line react-hooks/exhaustive-deps + [...dependencies], + ); +} + +function useNodeRef(onChange) { + const onChangeHandler = useEvent(onChange); + const node = useRef$2(null); + const setNodeRef = useCallback$2( + (element) => { + if (element !== node.current) { + onChangeHandler == null ? void 0 : onChangeHandler(element, node.current); + } + + node.current = element; + }, //eslint-disable-next-line + [], + ); + return [node, setNodeRef]; +} + +function usePrevious(value) { + const ref = useRef$2(); + useEffect$1(() => { + ref.current = value; + }, [value]); + return ref.current; +} + +let ids = {}; +function useUniqueId(prefix, value) { + return useMemo$1(() => { + if (value) { + return value; + } + + const id = ids[prefix] == null ? 0 : ids[prefix] + 1; + ids[prefix] = id; + return prefix + "-" + id; + }, [prefix, value]); +} + +function createAdjustmentFn(modifier) { + return function (object) { + for (var _len = arguments.length, adjustments = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + adjustments[_key - 1] = arguments[_key]; + } + + return adjustments.reduce( + (accumulator, adjustment) => { + const entries = Object.entries(adjustment); + + for (const [key, valueAdjustment] of entries) { + const value = accumulator[key]; + + if (value != null) { + accumulator[key] = value + modifier * valueAdjustment; + } + } + + return accumulator; + }, + { ...object }, + ); + }; +} + +const add = /*#__PURE__*/ createAdjustmentFn(1); +const subtract = /*#__PURE__*/ createAdjustmentFn(-1); + +function hasViewportRelativeCoordinates(event) { + return "clientX" in event && "clientY" in event; +} + +function isKeyboardEvent(event) { + if (!event) { + return false; + } + + const { KeyboardEvent } = getWindow(event.target); + return KeyboardEvent && event instanceof KeyboardEvent; +} + +function isTouchEvent(event) { + if (!event) { + return false; + } + + const { TouchEvent } = getWindow(event.target); + return TouchEvent && event instanceof TouchEvent; +} + +/** + * Returns the normalized x and y coordinates for mouse and touch events. + */ + +function getEventCoordinates(event) { + if (isTouchEvent(event)) { + if (event.touches && event.touches.length) { + const { clientX: x, clientY: y } = event.touches[0]; + return { + x, + y, + }; + } else if (event.changedTouches && event.changedTouches.length) { + const { clientX: x, clientY: y } = event.changedTouches[0]; + return { + x, + y, + }; + } + } + + if (hasViewportRelativeCoordinates(event)) { + return { + x: event.clientX, + y: event.clientY, + }; + } + + return null; +} + +const CSS = /*#__PURE__*/ Object.freeze({ + Translate: { + toString(transform) { + if (!transform) { + return; + } + + const { x, y } = transform; + return "translate3d(" + (x ? Math.round(x) : 0) + "px, " + (y ? Math.round(y) : 0) + "px, 0)"; + }, + }, + Scale: { + toString(transform) { + if (!transform) { + return; + } + + const { scaleX, scaleY } = transform; + return "scaleX(" + scaleX + ") scaleY(" + scaleY + ")"; + }, + }, + Transform: { + toString(transform) { + if (!transform) { + return; + } + + return [CSS.Translate.toString(transform), CSS.Scale.toString(transform)].join(" "); + }, + }, + Transition: { + toString(_ref) { + let { property, duration, easing } = _ref; + return property + " " + duration + "ms " + easing; + }, + }, +}); + +const SELECTOR = "a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]"; +function findFirstFocusableNode(element) { + if (element.matches(SELECTOR)) { + return element; + } + + return element.querySelector(SELECTOR); +} + +const React$3 = await importShared("react"); +const { useState: useState$1, useCallback: useCallback$1 } = React$3; + +const hiddenStyles = { + display: "none", +}; +function HiddenText(_ref) { + let { id, value } = _ref; + return React$3.createElement( + "div", + { + id: id, + style: hiddenStyles, + }, + value, + ); +} + +function LiveRegion(_ref) { + let { id, announcement, ariaLiveType = "assertive" } = _ref; + // Hide element visually but keep it readable by screen readers + const visuallyHidden = { + position: "fixed", + top: 0, + left: 0, + width: 1, + height: 1, + margin: -1, + border: 0, + padding: 0, + overflow: "hidden", + clip: "rect(0 0 0 0)", + clipPath: "inset(100%)", + whiteSpace: "nowrap", + }; + return React$3.createElement( + "div", + { + "id": id, + "style": visuallyHidden, + "role": "status", + "aria-live": ariaLiveType, + "aria-atomic": true, + }, + announcement, + ); +} + +function useAnnouncement() { + const [announcement, setAnnouncement] = useState$1(""); + const announce = useCallback$1((value) => { + if (value != null) { + setAnnouncement(value); + } + }, []); + return { + announce, + announcement, + }; +} + +const React$2 = await importShared("react"); +const { createContext: createContext$1, useContext: useContext$1, useEffect, useState, useCallback, useMemo, useRef: useRef$1, memo: memo$1, useReducer, cloneElement, forwardRef } = React$2; + +const { createPortal, unstable_batchedUpdates } = await importShared("react-dom"); + +const DndMonitorContext = /*#__PURE__*/ createContext$1(null); + +function useDndMonitor(listener) { + const registerListener = useContext$1(DndMonitorContext); + useEffect(() => { + if (!registerListener) { + throw new Error("useDndMonitor must be used within a children of "); + } + + const unsubscribe = registerListener(listener); + return unsubscribe; + }, [listener, registerListener]); +} + +function useDndMonitorProvider() { + const [listeners] = useState(() => new Set()); + const registerListener = useCallback( + (listener) => { + listeners.add(listener); + return () => listeners.delete(listener); + }, + [listeners], + ); + const dispatch = useCallback( + (_ref) => { + let { type, event } = _ref; + listeners.forEach((listener) => { + var _listener$type; + + return (_listener$type = listener[type]) == null ? void 0 : _listener$type.call(listener, event); + }); + }, + [listeners], + ); + return [dispatch, registerListener]; +} + +const defaultScreenReaderInstructions = { + draggable: + "\n To pick up a draggable item, press the space bar.\n While dragging, use the arrow keys to move the item.\n Press space again to drop the item in its new position, or press escape to cancel.\n ", +}; +const defaultAnnouncements = { + onDragStart(_ref) { + let { active } = _ref; + return "Picked up draggable item " + active.id + "."; + }, + + onDragOver(_ref2) { + let { active, over } = _ref2; + + if (over) { + return "Draggable item " + active.id + " was moved over droppable area " + over.id + "."; + } + + return "Draggable item " + active.id + " is no longer over a droppable area."; + }, + + onDragEnd(_ref3) { + let { active, over } = _ref3; + + if (over) { + return "Draggable item " + active.id + " was dropped over droppable area " + over.id; + } + + return "Draggable item " + active.id + " was dropped."; + }, + + onDragCancel(_ref4) { + let { active } = _ref4; + return "Dragging was cancelled. Draggable item " + active.id + " was dropped."; + }, +}; + +function Accessibility(_ref) { + let { announcements = defaultAnnouncements, container, hiddenTextDescribedById, screenReaderInstructions = defaultScreenReaderInstructions } = _ref; + const { announce, announcement } = useAnnouncement(); + const liveRegionId = useUniqueId("DndLiveRegion"); + const [mounted, setMounted] = useState(false); + useEffect(() => { + setMounted(true); + }, []); + useDndMonitor( + useMemo( + () => ({ + onDragStart(_ref2) { + let { active } = _ref2; + announce( + announcements.onDragStart({ + active, + }), + ); + }, + + onDragMove(_ref3) { + let { active, over } = _ref3; + + if (announcements.onDragMove) { + announce( + announcements.onDragMove({ + active, + over, + }), + ); + } + }, + + onDragOver(_ref4) { + let { active, over } = _ref4; + announce( + announcements.onDragOver({ + active, + over, + }), + ); + }, + + onDragEnd(_ref5) { + let { active, over } = _ref5; + announce( + announcements.onDragEnd({ + active, + over, + }), + ); + }, + + onDragCancel(_ref6) { + let { active, over } = _ref6; + announce( + announcements.onDragCancel({ + active, + over, + }), + ); + }, + }), + [announce, announcements], + ), + ); + + if (!mounted) { + return null; + } + + const markup = React$2.createElement( + React$2.Fragment, + null, + React$2.createElement(HiddenText, { + id: hiddenTextDescribedById, + value: screenReaderInstructions.draggable, + }), + React$2.createElement(LiveRegion, { + id: liveRegionId, + announcement: announcement, + }), + ); + return container ? createPortal(markup, container) : markup; +} + +var Action; + +(function (Action) { + Action["DragStart"] = "dragStart"; + Action["DragMove"] = "dragMove"; + Action["DragEnd"] = "dragEnd"; + Action["DragCancel"] = "dragCancel"; + Action["DragOver"] = "dragOver"; + Action["RegisterDroppable"] = "registerDroppable"; + Action["SetDroppableDisabled"] = "setDroppableDisabled"; + Action["UnregisterDroppable"] = "unregisterDroppable"; +})(Action || (Action = {})); + +function noop() {} + +function useSensor(sensor, options) { + return useMemo( + () => ({ + sensor, + options: options != null ? options : {}, + }), // eslint-disable-next-line react-hooks/exhaustive-deps + [sensor, options], + ); +} + +function useSensors() { + for (var _len = arguments.length, sensors = new Array(_len), _key = 0; _key < _len; _key++) { + sensors[_key] = arguments[_key]; + } + + return useMemo( + () => [...sensors].filter((sensor) => sensor != null), // eslint-disable-next-line react-hooks/exhaustive-deps + [...sensors], + ); +} + +const defaultCoordinates = /*#__PURE__*/ Object.freeze({ + x: 0, + y: 0, +}); + +/** + * Returns the distance between two points + */ +function distanceBetween(p1, p2) { + return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); +} + +function getRelativeTransformOrigin(event, rect) { + const eventCoordinates = getEventCoordinates(event); + + if (!eventCoordinates) { + return "0 0"; + } + + const transformOrigin = { + x: ((eventCoordinates.x - rect.left) / rect.width) * 100, + y: ((eventCoordinates.y - rect.top) / rect.height) * 100, + }; + return transformOrigin.x + "% " + transformOrigin.y + "%"; +} + +/** + * Sort collisions from smallest to greatest value + */ +function sortCollisionsAsc(_ref, _ref2) { + let { + data: { value: a }, + } = _ref; + let { + data: { value: b }, + } = _ref2; + return a - b; +} +/** + * Sort collisions from greatest to smallest value + */ + +function sortCollisionsDesc(_ref3, _ref4) { + let { + data: { value: a }, + } = _ref3; + let { + data: { value: b }, + } = _ref4; + return b - a; +} +/** + * Returns the coordinates of the corners of a given rectangle: + * [TopLeft {x, y}, TopRight {x, y}, BottomLeft {x, y}, BottomRight {x, y}] + */ + +function cornersOfRectangle(_ref5) { + let { left, top, height, width } = _ref5; + return [ + { + x: left, + y: top, + }, + { + x: left + width, + y: top, + }, + { + x: left, + y: top + height, + }, + { + x: left + width, + y: top + height, + }, + ]; +} +function getFirstCollision(collisions, property) { + if (!collisions || collisions.length === 0) { + return null; + } + + const [firstCollision] = collisions; + return firstCollision[property]; +} + +/** + * Returns the intersecting rectangle area between two rectangles + */ + +function getIntersectionRatio(entry, target) { + const top = Math.max(target.top, entry.top); + const left = Math.max(target.left, entry.left); + const right = Math.min(target.left + target.width, entry.left + entry.width); + const bottom = Math.min(target.top + target.height, entry.top + entry.height); + const width = right - left; + const height = bottom - top; + + if (left < right && top < bottom) { + const targetArea = target.width * target.height; + const entryArea = entry.width * entry.height; + const intersectionArea = width * height; + const intersectionRatio = intersectionArea / (targetArea + entryArea - intersectionArea); + return Number(intersectionRatio.toFixed(4)); + } // Rectangles do not overlap, or overlap has an area of zero (edge/corner overlap) + + return 0; +} +/** + * Returns the rectangles that has the greatest intersection area with a given + * rectangle in an array of rectangles. + */ + +const rectIntersection = (_ref) => { + let { collisionRect, droppableRects, droppableContainers } = _ref; + const collisions = []; + + for (const droppableContainer of droppableContainers) { + const { id } = droppableContainer; + const rect = droppableRects.get(id); + + if (rect) { + const intersectionRatio = getIntersectionRatio(rect, collisionRect); + + if (intersectionRatio > 0) { + collisions.push({ + id, + data: { + droppableContainer, + value: intersectionRatio, + }, + }); + } + } + } + + return collisions.sort(sortCollisionsDesc); +}; + +/** + * Check if a given point is contained within a bounding rectangle + */ + +function isPointWithinRect(point, rect) { + const { top, left, bottom, right } = rect; + return top <= point.y && point.y <= bottom && left <= point.x && point.x <= right; +} +/** + * Returns the rectangles that the pointer is hovering over + */ + +const pointerWithin = (_ref) => { + let { droppableContainers, droppableRects, pointerCoordinates } = _ref; + + if (!pointerCoordinates) { + return []; + } + + const collisions = []; + + for (const droppableContainer of droppableContainers) { + const { id } = droppableContainer; + const rect = droppableRects.get(id); + + if (rect && isPointWithinRect(pointerCoordinates, rect)) { + /* There may be more than a single rectangle intersecting + * with the pointer coordinates. In order to sort the + * colliding rectangles, we measure the distance between + * the pointer and the corners of the intersecting rectangle + */ + const corners = cornersOfRectangle(rect); + const distances = corners.reduce((accumulator, corner) => { + return accumulator + distanceBetween(pointerCoordinates, corner); + }, 0); + const effectiveDistance = Number((distances / 4).toFixed(4)); + collisions.push({ + id, + data: { + droppableContainer, + value: effectiveDistance, + }, + }); + } + } + + return collisions.sort(sortCollisionsAsc); +}; + +function adjustScale(transform, rect1, rect2) { + return { ...transform, scaleX: rect1 && rect2 ? rect1.width / rect2.width : 1, scaleY: rect1 && rect2 ? rect1.height / rect2.height : 1 }; +} + +function getRectDelta(rect1, rect2) { + return rect1 && rect2 ? + { + x: rect1.left - rect2.left, + y: rect1.top - rect2.top, + } + : defaultCoordinates; +} + +function createRectAdjustmentFn(modifier) { + return function adjustClientRect(rect) { + for (var _len = arguments.length, adjustments = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + adjustments[_key - 1] = arguments[_key]; + } + + return adjustments.reduce( + (acc, adjustment) => ({ + ...acc, + top: acc.top + modifier * adjustment.y, + bottom: acc.bottom + modifier * adjustment.y, + left: acc.left + modifier * adjustment.x, + right: acc.right + modifier * adjustment.x, + }), + { ...rect }, + ); + }; +} +const getAdjustedRect = /*#__PURE__*/ createRectAdjustmentFn(1); + +function parseTransform(transform) { + if (transform.startsWith("matrix3d(")) { + const transformArray = transform.slice(9, -1).split(/, /); + return { + x: +transformArray[12], + y: +transformArray[13], + scaleX: +transformArray[0], + scaleY: +transformArray[5], + }; + } else if (transform.startsWith("matrix(")) { + const transformArray = transform.slice(7, -1).split(/, /); + return { + x: +transformArray[4], + y: +transformArray[5], + scaleX: +transformArray[0], + scaleY: +transformArray[3], + }; + } + + return null; +} + +function inverseTransform(rect, transform, transformOrigin) { + const parsedTransform = parseTransform(transform); + + if (!parsedTransform) { + return rect; + } + + const { scaleX, scaleY, x: translateX, y: translateY } = parsedTransform; + const x = rect.left - translateX - (1 - scaleX) * parseFloat(transformOrigin); + const y = rect.top - translateY - (1 - scaleY) * parseFloat(transformOrigin.slice(transformOrigin.indexOf(" ") + 1)); + const w = scaleX ? rect.width / scaleX : rect.width; + const h = scaleY ? rect.height / scaleY : rect.height; + return { + width: w, + height: h, + top: y, + right: x + w, + bottom: y + h, + left: x, + }; +} + +const defaultOptions$1 = { + ignoreTransform: false, +}; +/** + * Returns the bounding client rect of an element relative to the viewport. + */ + +function getClientRect(element, options) { + if (options === void 0) { + options = defaultOptions$1; + } + + let rect = element.getBoundingClientRect(); + + if (options.ignoreTransform) { + const { transform, transformOrigin } = getWindow(element).getComputedStyle(element); + + if (transform) { + rect = inverseTransform(rect, transform, transformOrigin); + } + } + + const { top, left, width, height, bottom, right } = rect; + return { + top, + left, + width, + height, + bottom, + right, + }; +} +/** + * Returns the bounding client rect of an element relative to the viewport. + * + * @remarks + * The ClientRect returned by this method does not take into account transforms + * applied to the element it measures. + * + */ + +function getTransformAgnosticClientRect(element) { + return getClientRect(element, { + ignoreTransform: true, + }); +} + +function getWindowClientRect(element) { + const width = element.innerWidth; + const height = element.innerHeight; + return { + top: 0, + left: 0, + right: width, + bottom: height, + width, + height, + }; +} + +function isFixed(node, computedStyle) { + if (computedStyle === void 0) { + computedStyle = getWindow(node).getComputedStyle(node); + } + + return computedStyle.position === "fixed"; +} + +function isScrollable(element, computedStyle) { + if (computedStyle === void 0) { + computedStyle = getWindow(element).getComputedStyle(element); + } + + const overflowRegex = /(auto|scroll|overlay)/; + const properties = ["overflow", "overflowX", "overflowY"]; + return properties.some((property) => { + const value = computedStyle[property]; + return typeof value === "string" ? overflowRegex.test(value) : false; + }); +} + +function getScrollableAncestors(element, limit) { + const scrollParents = []; + + function findScrollableAncestors(node) { + if (limit != null && scrollParents.length >= limit) { + return scrollParents; + } + + if (!node) { + return scrollParents; + } + + if (isDocument(node) && node.scrollingElement != null && !scrollParents.includes(node.scrollingElement)) { + scrollParents.push(node.scrollingElement); + return scrollParents; + } + + if (!isHTMLElement(node) || isSVGElement(node)) { + return scrollParents; + } + + if (scrollParents.includes(node)) { + return scrollParents; + } + + const computedStyle = getWindow(element).getComputedStyle(node); + + if (node !== element) { + if (isScrollable(node, computedStyle)) { + scrollParents.push(node); + } + } + + if (isFixed(node, computedStyle)) { + return scrollParents; + } + + return findScrollableAncestors(node.parentNode); + } + + if (!element) { + return scrollParents; + } + + return findScrollableAncestors(element); +} +function getFirstScrollableAncestor(node) { + const [firstScrollableAncestor] = getScrollableAncestors(node, 1); + return firstScrollableAncestor != null ? firstScrollableAncestor : null; +} + +function getScrollableElement(element) { + if (!canUseDOM || !element) { + return null; + } + + if (isWindow(element)) { + return element; + } + + if (!isNode$1(element)) { + return null; + } + + if (isDocument(element) || element === getOwnerDocument(element).scrollingElement) { + return window; + } + + if (isHTMLElement(element)) { + return element; + } + + return null; +} + +function getScrollXCoordinate(element) { + if (isWindow(element)) { + return element.scrollX; + } + + return element.scrollLeft; +} +function getScrollYCoordinate(element) { + if (isWindow(element)) { + return element.scrollY; + } + + return element.scrollTop; +} +function getScrollCoordinates(element) { + return { + x: getScrollXCoordinate(element), + y: getScrollYCoordinate(element), + }; +} + +var Direction; + +(function (Direction) { + Direction[(Direction["Forward"] = 1)] = "Forward"; + Direction[(Direction["Backward"] = -1)] = "Backward"; +})(Direction || (Direction = {})); + +function isDocumentScrollingElement(element) { + if (!canUseDOM || !element) { + return false; + } + + return element === document.scrollingElement; +} + +function getScrollPosition(scrollingContainer) { + const minScroll = { + x: 0, + y: 0, + }; + const dimensions = + isDocumentScrollingElement(scrollingContainer) ? + { + height: window.innerHeight, + width: window.innerWidth, + } + : { + height: scrollingContainer.clientHeight, + width: scrollingContainer.clientWidth, + }; + const maxScroll = { + x: scrollingContainer.scrollWidth - dimensions.width, + y: scrollingContainer.scrollHeight - dimensions.height, + }; + const isTop = scrollingContainer.scrollTop <= minScroll.y; + const isLeft = scrollingContainer.scrollLeft <= minScroll.x; + const isBottom = scrollingContainer.scrollTop >= maxScroll.y; + const isRight = scrollingContainer.scrollLeft >= maxScroll.x; + return { + isTop, + isLeft, + isBottom, + isRight, + maxScroll, + minScroll, + }; +} + +const defaultThreshold = { + x: 0.2, + y: 0.2, +}; +function getScrollDirectionAndSpeed(scrollContainer, scrollContainerRect, _ref, acceleration, thresholdPercentage) { + let { top, left, right, bottom } = _ref; + + if (acceleration === void 0) { + acceleration = 10; + } + + if (thresholdPercentage === void 0) { + thresholdPercentage = defaultThreshold; + } + + const { isTop, isBottom, isLeft, isRight } = getScrollPosition(scrollContainer); + const direction = { + x: 0, + y: 0, + }; + const speed = { + x: 0, + y: 0, + }; + const threshold = { + height: scrollContainerRect.height * thresholdPercentage.y, + width: scrollContainerRect.width * thresholdPercentage.x, + }; + + if (!isTop && top <= scrollContainerRect.top + threshold.height) { + // Scroll Up + direction.y = Direction.Backward; + speed.y = acceleration * Math.abs((scrollContainerRect.top + threshold.height - top) / threshold.height); + } else if (!isBottom && bottom >= scrollContainerRect.bottom - threshold.height) { + // Scroll Down + direction.y = Direction.Forward; + speed.y = acceleration * Math.abs((scrollContainerRect.bottom - threshold.height - bottom) / threshold.height); + } + + if (!isRight && right >= scrollContainerRect.right - threshold.width) { + // Scroll Right + direction.x = Direction.Forward; + speed.x = acceleration * Math.abs((scrollContainerRect.right - threshold.width - right) / threshold.width); + } else if (!isLeft && left <= scrollContainerRect.left + threshold.width) { + // Scroll Left + direction.x = Direction.Backward; + speed.x = acceleration * Math.abs((scrollContainerRect.left + threshold.width - left) / threshold.width); + } + + return { + direction, + speed, + }; +} + +function getScrollElementRect(element) { + if (element === document.scrollingElement) { + const { innerWidth, innerHeight } = window; + return { + top: 0, + left: 0, + right: innerWidth, + bottom: innerHeight, + width: innerWidth, + height: innerHeight, + }; + } + + const { top, left, right, bottom } = element.getBoundingClientRect(); + return { + top, + left, + right, + bottom, + width: element.clientWidth, + height: element.clientHeight, + }; +} + +function getScrollOffsets(scrollableAncestors) { + return scrollableAncestors.reduce((acc, node) => { + return add(acc, getScrollCoordinates(node)); + }, defaultCoordinates); +} +function getScrollXOffset(scrollableAncestors) { + return scrollableAncestors.reduce((acc, node) => { + return acc + getScrollXCoordinate(node); + }, 0); +} +function getScrollYOffset(scrollableAncestors) { + return scrollableAncestors.reduce((acc, node) => { + return acc + getScrollYCoordinate(node); + }, 0); +} + +function scrollIntoViewIfNeeded(element, measure) { + if (measure === void 0) { + measure = getClientRect; + } + + if (!element) { + return; + } + + const { top, left, bottom, right } = measure(element); + const firstScrollableAncestor = getFirstScrollableAncestor(element); + + if (!firstScrollableAncestor) { + return; + } + + if (bottom <= 0 || right <= 0 || top >= window.innerHeight || left >= window.innerWidth) { + element.scrollIntoView({ + block: "center", + inline: "center", + }); + } +} + +const properties = [ + ["x", ["left", "right"], getScrollXOffset], + ["y", ["top", "bottom"], getScrollYOffset], +]; +class Rect { + constructor(rect, element) { + this.rect = void 0; + this.width = void 0; + this.height = void 0; + this.top = void 0; + this.bottom = void 0; + this.right = void 0; + this.left = void 0; + const scrollableAncestors = getScrollableAncestors(element); + const scrollOffsets = getScrollOffsets(scrollableAncestors); + this.rect = { ...rect }; + this.width = rect.width; + this.height = rect.height; + + for (const [axis, keys, getScrollOffset] of properties) { + for (const key of keys) { + Object.defineProperty(this, key, { + get: () => { + const currentOffsets = getScrollOffset(scrollableAncestors); + const scrollOffsetsDeltla = scrollOffsets[axis] - currentOffsets; + return this.rect[key] + scrollOffsetsDeltla; + }, + enumerable: true, + }); + } + } + + Object.defineProperty(this, "rect", { + enumerable: false, + }); + } +} + +class Listeners { + constructor(target) { + this.target = void 0; + this.listeners = []; + + this.removeAll = () => { + this.listeners.forEach((listener) => { + var _this$target; + + return (_this$target = this.target) == null ? void 0 : _this$target.removeEventListener(...listener); + }); + }; + + this.target = target; + } + + add(eventName, handler, options) { + var _this$target2; + + (_this$target2 = this.target) == null ? void 0 : _this$target2.addEventListener(eventName, handler, options); + this.listeners.push([eventName, handler, options]); + } +} + +function getEventListenerTarget(target) { + // If the `event.target` element is removed from the document events will still be targeted + // at it, and hence won't always bubble up to the window or document anymore. + // If there is any risk of an element being removed while it is being dragged, + // the best practice is to attach the event listeners directly to the target. + // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget + const { EventTarget } = getWindow(target); + return target instanceof EventTarget ? target : getOwnerDocument(target); +} + +function hasExceededDistance(delta, measurement) { + const dx = Math.abs(delta.x); + const dy = Math.abs(delta.y); + + if (typeof measurement === "number") { + return Math.sqrt(dx ** 2 + dy ** 2) > measurement; + } + + if ("x" in measurement && "y" in measurement) { + return dx > measurement.x && dy > measurement.y; + } + + if ("x" in measurement) { + return dx > measurement.x; + } + + if ("y" in measurement) { + return dy > measurement.y; + } + + return false; +} + +var EventName; + +(function (EventName) { + EventName["Click"] = "click"; + EventName["DragStart"] = "dragstart"; + EventName["Keydown"] = "keydown"; + EventName["ContextMenu"] = "contextmenu"; + EventName["Resize"] = "resize"; + EventName["SelectionChange"] = "selectionchange"; + EventName["VisibilityChange"] = "visibilitychange"; +})(EventName || (EventName = {})); + +function preventDefault(event) { + event.preventDefault(); +} +function stopPropagation(event) { + event.stopPropagation(); +} + +var KeyboardCode; + +(function (KeyboardCode) { + KeyboardCode["Space"] = "Space"; + KeyboardCode["Down"] = "ArrowDown"; + KeyboardCode["Right"] = "ArrowRight"; + KeyboardCode["Left"] = "ArrowLeft"; + KeyboardCode["Up"] = "ArrowUp"; + KeyboardCode["Esc"] = "Escape"; + KeyboardCode["Enter"] = "Enter"; + KeyboardCode["Tab"] = "Tab"; +})(KeyboardCode || (KeyboardCode = {})); + +const defaultKeyboardCodes = { + start: [KeyboardCode.Space, KeyboardCode.Enter], + cancel: [KeyboardCode.Esc], + end: [KeyboardCode.Space, KeyboardCode.Enter, KeyboardCode.Tab], +}; +const defaultKeyboardCoordinateGetter = (event, _ref) => { + let { currentCoordinates } = _ref; + + switch (event.code) { + case KeyboardCode.Right: + return { ...currentCoordinates, x: currentCoordinates.x + 25 }; + + case KeyboardCode.Left: + return { ...currentCoordinates, x: currentCoordinates.x - 25 }; + + case KeyboardCode.Down: + return { ...currentCoordinates, y: currentCoordinates.y + 25 }; + + case KeyboardCode.Up: + return { ...currentCoordinates, y: currentCoordinates.y - 25 }; + } + + return undefined; +}; + +class KeyboardSensor { + constructor(props) { + this.props = void 0; + this.autoScrollEnabled = false; + this.referenceCoordinates = void 0; + this.listeners = void 0; + this.windowListeners = void 0; + this.props = props; + const { + event: { target }, + } = props; + this.props = props; + this.listeners = new Listeners(getOwnerDocument(target)); + this.windowListeners = new Listeners(getWindow(target)); + this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleCancel = this.handleCancel.bind(this); + this.attach(); + } + + attach() { + this.handleStart(); + this.windowListeners.add(EventName.Resize, this.handleCancel); + this.windowListeners.add(EventName.VisibilityChange, this.handleCancel); + setTimeout(() => this.listeners.add(EventName.Keydown, this.handleKeyDown)); + } + + handleStart() { + const { activeNode, onStart } = this.props; + const node = activeNode.node.current; + + if (node) { + scrollIntoViewIfNeeded(node); + } + + onStart(defaultCoordinates); + } + + handleKeyDown(event) { + if (isKeyboardEvent(event)) { + const { active, context, options } = this.props; + const { keyboardCodes = defaultKeyboardCodes, coordinateGetter = defaultKeyboardCoordinateGetter, scrollBehavior = "smooth" } = options; + const { code } = event; + + if (keyboardCodes.end.includes(code)) { + this.handleEnd(event); + return; + } + + if (keyboardCodes.cancel.includes(code)) { + this.handleCancel(event); + return; + } + + const { collisionRect } = context.current; + const currentCoordinates = + collisionRect ? + { + x: collisionRect.left, + y: collisionRect.top, + } + : defaultCoordinates; + + if (!this.referenceCoordinates) { + this.referenceCoordinates = currentCoordinates; + } + + const newCoordinates = coordinateGetter(event, { + active, + context: context.current, + currentCoordinates, + }); + + if (newCoordinates) { + const coordinatesDelta = subtract(newCoordinates, currentCoordinates); + const scrollDelta = { + x: 0, + y: 0, + }; + const { scrollableAncestors } = context.current; + + for (const scrollContainer of scrollableAncestors) { + const direction = event.code; + const { isTop, isRight, isLeft, isBottom, maxScroll, minScroll } = getScrollPosition(scrollContainer); + const scrollElementRect = getScrollElementRect(scrollContainer); + const clampedCoordinates = { + x: Math.min( + direction === KeyboardCode.Right ? scrollElementRect.right - scrollElementRect.width / 2 : scrollElementRect.right, + Math.max(direction === KeyboardCode.Right ? scrollElementRect.left : scrollElementRect.left + scrollElementRect.width / 2, newCoordinates.x), + ), + y: Math.min( + direction === KeyboardCode.Down ? scrollElementRect.bottom - scrollElementRect.height / 2 : scrollElementRect.bottom, + Math.max(direction === KeyboardCode.Down ? scrollElementRect.top : scrollElementRect.top + scrollElementRect.height / 2, newCoordinates.y), + ), + }; + const canScrollX = (direction === KeyboardCode.Right && !isRight) || (direction === KeyboardCode.Left && !isLeft); + const canScrollY = (direction === KeyboardCode.Down && !isBottom) || (direction === KeyboardCode.Up && !isTop); + + if (canScrollX && clampedCoordinates.x !== newCoordinates.x) { + const newScrollCoordinates = scrollContainer.scrollLeft + coordinatesDelta.x; + const canScrollToNewCoordinates = + (direction === KeyboardCode.Right && newScrollCoordinates <= maxScroll.x) || (direction === KeyboardCode.Left && newScrollCoordinates >= minScroll.x); + + if (canScrollToNewCoordinates && !coordinatesDelta.y) { + // We don't need to update coordinates, the scroll adjustment alone will trigger + // logic to auto-detect the new container we are over + scrollContainer.scrollTo({ + left: newScrollCoordinates, + behavior: scrollBehavior, + }); + return; + } + + if (canScrollToNewCoordinates) { + scrollDelta.x = scrollContainer.scrollLeft - newScrollCoordinates; + } else { + scrollDelta.x = direction === KeyboardCode.Right ? scrollContainer.scrollLeft - maxScroll.x : scrollContainer.scrollLeft - minScroll.x; + } + + if (scrollDelta.x) { + scrollContainer.scrollBy({ + left: -scrollDelta.x, + behavior: scrollBehavior, + }); + } + + break; + } else if (canScrollY && clampedCoordinates.y !== newCoordinates.y) { + const newScrollCoordinates = scrollContainer.scrollTop + coordinatesDelta.y; + const canScrollToNewCoordinates = + (direction === KeyboardCode.Down && newScrollCoordinates <= maxScroll.y) || (direction === KeyboardCode.Up && newScrollCoordinates >= minScroll.y); + + if (canScrollToNewCoordinates && !coordinatesDelta.x) { + // We don't need to update coordinates, the scroll adjustment alone will trigger + // logic to auto-detect the new container we are over + scrollContainer.scrollTo({ + top: newScrollCoordinates, + behavior: scrollBehavior, + }); + return; + } + + if (canScrollToNewCoordinates) { + scrollDelta.y = scrollContainer.scrollTop - newScrollCoordinates; + } else { + scrollDelta.y = direction === KeyboardCode.Down ? scrollContainer.scrollTop - maxScroll.y : scrollContainer.scrollTop - minScroll.y; + } + + if (scrollDelta.y) { + scrollContainer.scrollBy({ + top: -scrollDelta.y, + behavior: scrollBehavior, + }); + } + + break; + } + } + + this.handleMove(event, add(subtract(newCoordinates, this.referenceCoordinates), scrollDelta)); + } + } + } + + handleMove(event, coordinates) { + const { onMove } = this.props; + event.preventDefault(); + onMove(coordinates); + } + + handleEnd(event) { + const { onEnd } = this.props; + event.preventDefault(); + this.detach(); + onEnd(); + } + + handleCancel(event) { + const { onCancel } = this.props; + event.preventDefault(); + this.detach(); + onCancel(); + } + + detach() { + this.listeners.removeAll(); + this.windowListeners.removeAll(); + } +} +KeyboardSensor.activators = [ + { + eventName: "onKeyDown", + handler: (event, _ref, _ref2) => { + let { keyboardCodes = defaultKeyboardCodes, onActivation } = _ref; + let { active } = _ref2; + const { code } = event.nativeEvent; + + if (keyboardCodes.start.includes(code)) { + const activator = active.activatorNode.current; + + if (activator && event.target !== activator) { + return false; + } + + event.preventDefault(); + onActivation == null ? void 0 : ( + onActivation({ + event: event.nativeEvent, + }) + ); + return true; + } + + return false; + }, + }, +]; + +function isDistanceConstraint(constraint) { + return Boolean(constraint && "distance" in constraint); +} + +function isDelayConstraint(constraint) { + return Boolean(constraint && "delay" in constraint); +} + +class AbstractPointerSensor { + constructor(props, events, listenerTarget) { + var _getEventCoordinates; + + if (listenerTarget === void 0) { + listenerTarget = getEventListenerTarget(props.event.target); + } + + this.props = void 0; + this.events = void 0; + this.autoScrollEnabled = true; + this.document = void 0; + this.activated = false; + this.initialCoordinates = void 0; + this.timeoutId = null; + this.listeners = void 0; + this.documentListeners = void 0; + this.windowListeners = void 0; + this.props = props; + this.events = events; + const { event } = props; + const { target } = event; + this.props = props; + this.events = events; + this.document = getOwnerDocument(target); + this.documentListeners = new Listeners(this.document); + this.listeners = new Listeners(listenerTarget); + this.windowListeners = new Listeners(getWindow(target)); + this.initialCoordinates = (_getEventCoordinates = getEventCoordinates(event)) != null ? _getEventCoordinates : defaultCoordinates; + this.handleStart = this.handleStart.bind(this); + this.handleMove = this.handleMove.bind(this); + this.handleEnd = this.handleEnd.bind(this); + this.handleCancel = this.handleCancel.bind(this); + this.handleKeydown = this.handleKeydown.bind(this); + this.removeTextSelection = this.removeTextSelection.bind(this); + this.attach(); + } + + attach() { + const { + events, + props: { + options: { activationConstraint, bypassActivationConstraint }, + }, + } = this; + this.listeners.add(events.move.name, this.handleMove, { + passive: false, + }); + this.listeners.add(events.end.name, this.handleEnd); + + if (events.cancel) { + this.listeners.add(events.cancel.name, this.handleCancel); + } + + this.windowListeners.add(EventName.Resize, this.handleCancel); + this.windowListeners.add(EventName.DragStart, preventDefault); + this.windowListeners.add(EventName.VisibilityChange, this.handleCancel); + this.windowListeners.add(EventName.ContextMenu, preventDefault); + this.documentListeners.add(EventName.Keydown, this.handleKeydown); + + if (activationConstraint) { + if ( + bypassActivationConstraint != null && + bypassActivationConstraint({ + event: this.props.event, + activeNode: this.props.activeNode, + options: this.props.options, + }) + ) { + return this.handleStart(); + } + + if (isDelayConstraint(activationConstraint)) { + this.timeoutId = setTimeout(this.handleStart, activationConstraint.delay); + this.handlePending(activationConstraint); + return; + } + + if (isDistanceConstraint(activationConstraint)) { + this.handlePending(activationConstraint); + return; + } + } + + this.handleStart(); + } + + detach() { + this.listeners.removeAll(); + this.windowListeners.removeAll(); // Wait until the next event loop before removing document listeners + // This is necessary because we listen for `click` and `selection` events on the document + + setTimeout(this.documentListeners.removeAll, 50); + + if (this.timeoutId !== null) { + clearTimeout(this.timeoutId); + this.timeoutId = null; + } + } + + handlePending(constraint, offset) { + const { active, onPending } = this.props; + onPending(active, constraint, this.initialCoordinates, offset); + } + + handleStart() { + const { initialCoordinates } = this; + const { onStart } = this.props; + + if (initialCoordinates) { + this.activated = true; // Stop propagation of click events once activation constraints are met + + this.documentListeners.add(EventName.Click, stopPropagation, { + capture: true, + }); // Remove any text selection from the document + + this.removeTextSelection(); // Prevent further text selection while dragging + + this.documentListeners.add(EventName.SelectionChange, this.removeTextSelection); + onStart(initialCoordinates); + } + } + + handleMove(event) { + var _getEventCoordinates2; + + const { activated, initialCoordinates, props } = this; + const { + onMove, + options: { activationConstraint }, + } = props; + + if (!initialCoordinates) { + return; + } + + const coordinates = (_getEventCoordinates2 = getEventCoordinates(event)) != null ? _getEventCoordinates2 : defaultCoordinates; + const delta = subtract(initialCoordinates, coordinates); // Constraint validation + + if (!activated && activationConstraint) { + if (isDistanceConstraint(activationConstraint)) { + if (activationConstraint.tolerance != null && hasExceededDistance(delta, activationConstraint.tolerance)) { + return this.handleCancel(); + } + + if (hasExceededDistance(delta, activationConstraint.distance)) { + return this.handleStart(); + } + } + + if (isDelayConstraint(activationConstraint)) { + if (hasExceededDistance(delta, activationConstraint.tolerance)) { + return this.handleCancel(); + } + } + + this.handlePending(activationConstraint, delta); + return; + } + + if (event.cancelable) { + event.preventDefault(); + } + + onMove(coordinates); + } + + handleEnd() { + const { onAbort, onEnd } = this.props; + this.detach(); + + if (!this.activated) { + onAbort(this.props.active); + } + + onEnd(); + } + + handleCancel() { + const { onAbort, onCancel } = this.props; + this.detach(); + + if (!this.activated) { + onAbort(this.props.active); + } + + onCancel(); + } + + handleKeydown(event) { + if (event.code === KeyboardCode.Esc) { + this.handleCancel(); + } + } + + removeTextSelection() { + var _this$document$getSel; + + (_this$document$getSel = this.document.getSelection()) == null ? void 0 : _this$document$getSel.removeAllRanges(); + } +} + +const events = { + cancel: { + name: "pointercancel", + }, + move: { + name: "pointermove", + }, + end: { + name: "pointerup", + }, +}; +class PointerSensor extends AbstractPointerSensor { + constructor(props) { + const { event } = props; // Pointer events stop firing if the target is unmounted while dragging + // Therefore we attach listeners to the owner document instead + + const listenerTarget = getOwnerDocument(event.target); + super(props, events, listenerTarget); + } +} +PointerSensor.activators = [ + { + eventName: "onPointerDown", + handler: (_ref, _ref2) => { + let { nativeEvent: event } = _ref; + let { onActivation } = _ref2; + + if (!event.isPrimary || event.button !== 0) { + return false; + } + + onActivation == null ? void 0 : ( + onActivation({ + event, + }) + ); + return true; + }, + }, +]; + +const events$1 = { + move: { + name: "mousemove", + }, + end: { + name: "mouseup", + }, +}; +var MouseButton; + +(function (MouseButton) { + MouseButton[(MouseButton["RightClick"] = 2)] = "RightClick"; +})(MouseButton || (MouseButton = {})); + +class MouseSensor extends AbstractPointerSensor { + constructor(props) { + super(props, events$1, getOwnerDocument(props.event.target)); + } +} +MouseSensor.activators = [ + { + eventName: "onMouseDown", + handler: (_ref, _ref2) => { + let { nativeEvent: event } = _ref; + let { onActivation } = _ref2; + + if (event.button === MouseButton.RightClick) { + return false; + } + + onActivation == null ? void 0 : ( + onActivation({ + event, + }) + ); + return true; + }, + }, +]; + +const events$2 = { + cancel: { + name: "touchcancel", + }, + move: { + name: "touchmove", + }, + end: { + name: "touchend", + }, +}; +class TouchSensor extends AbstractPointerSensor { + constructor(props) { + super(props, events$2); + } + + static setup() { + // Adding a non-capture and non-passive `touchmove` listener in order + // to force `event.preventDefault()` calls to work in dynamically added + // touchmove event handlers. This is required for iOS Safari. + window.addEventListener(events$2.move.name, noop, { + capture: false, + passive: false, + }); + return function teardown() { + window.removeEventListener(events$2.move.name, noop); + }; // We create a new handler because the teardown function of another sensor + // could remove our event listener if we use a referentially equal listener. + + function noop() {} + } +} +TouchSensor.activators = [ + { + eventName: "onTouchStart", + handler: (_ref, _ref2) => { + let { nativeEvent: event } = _ref; + let { onActivation } = _ref2; + const { touches } = event; + + if (touches.length > 1) { + return false; + } + + onActivation == null ? void 0 : ( + onActivation({ + event, + }) + ); + return true; + }, + }, +]; + +var AutoScrollActivator; + +(function (AutoScrollActivator) { + AutoScrollActivator[(AutoScrollActivator["Pointer"] = 0)] = "Pointer"; + AutoScrollActivator[(AutoScrollActivator["DraggableRect"] = 1)] = "DraggableRect"; +})(AutoScrollActivator || (AutoScrollActivator = {})); + +var TraversalOrder; + +(function (TraversalOrder) { + TraversalOrder[(TraversalOrder["TreeOrder"] = 0)] = "TreeOrder"; + TraversalOrder[(TraversalOrder["ReversedTreeOrder"] = 1)] = "ReversedTreeOrder"; +})(TraversalOrder || (TraversalOrder = {})); + +function useAutoScroller(_ref) { + let { + acceleration, + activator = AutoScrollActivator.Pointer, + canScroll, + draggingRect, + enabled, + interval = 5, + order = TraversalOrder.TreeOrder, + pointerCoordinates, + scrollableAncestors, + scrollableAncestorRects, + delta, + threshold, + } = _ref; + const scrollIntent = useScrollIntent({ + delta, + disabled: !enabled, + }); + const [setAutoScrollInterval, clearAutoScrollInterval] = useInterval(); + const scrollSpeed = useRef$1({ + x: 0, + y: 0, + }); + const scrollDirection = useRef$1({ + x: 0, + y: 0, + }); + const rect = useMemo(() => { + switch (activator) { + case AutoScrollActivator.Pointer: + return pointerCoordinates ? + { + top: pointerCoordinates.y, + bottom: pointerCoordinates.y, + left: pointerCoordinates.x, + right: pointerCoordinates.x, + } + : null; + + case AutoScrollActivator.DraggableRect: + return draggingRect; + } + }, [activator, draggingRect, pointerCoordinates]); + const scrollContainerRef = useRef$1(null); + const autoScroll = useCallback(() => { + const scrollContainer = scrollContainerRef.current; + + if (!scrollContainer) { + return; + } + + const scrollLeft = scrollSpeed.current.x * scrollDirection.current.x; + const scrollTop = scrollSpeed.current.y * scrollDirection.current.y; + scrollContainer.scrollBy(scrollLeft, scrollTop); + }, []); + const sortedScrollableAncestors = useMemo(() => (order === TraversalOrder.TreeOrder ? [...scrollableAncestors].reverse() : scrollableAncestors), [order, scrollableAncestors]); + useEffect( + () => { + if (!enabled || !scrollableAncestors.length || !rect) { + clearAutoScrollInterval(); + return; + } + + for (const scrollContainer of sortedScrollableAncestors) { + if ((canScroll == null ? void 0 : canScroll(scrollContainer)) === false) { + continue; + } + + const index = scrollableAncestors.indexOf(scrollContainer); + const scrollContainerRect = scrollableAncestorRects[index]; + + if (!scrollContainerRect) { + continue; + } + + const { direction, speed } = getScrollDirectionAndSpeed(scrollContainer, scrollContainerRect, rect, acceleration, threshold); + + for (const axis of ["x", "y"]) { + if (!scrollIntent[axis][direction[axis]]) { + speed[axis] = 0; + direction[axis] = 0; + } + } + + if (speed.x > 0 || speed.y > 0) { + clearAutoScrollInterval(); + scrollContainerRef.current = scrollContainer; + setAutoScrollInterval(autoScroll, interval); + scrollSpeed.current = speed; + scrollDirection.current = direction; + return; + } + } + + scrollSpeed.current = { + x: 0, + y: 0, + }; + scrollDirection.current = { + x: 0, + y: 0, + }; + clearAutoScrollInterval(); + }, // eslint-disable-next-line react-hooks/exhaustive-deps + [ + acceleration, + autoScroll, + canScroll, + clearAutoScrollInterval, + enabled, + interval, // eslint-disable-next-line react-hooks/exhaustive-deps + JSON.stringify(rect), // eslint-disable-next-line react-hooks/exhaustive-deps + JSON.stringify(scrollIntent), + setAutoScrollInterval, + scrollableAncestors, + sortedScrollableAncestors, + scrollableAncestorRects, // eslint-disable-next-line react-hooks/exhaustive-deps + JSON.stringify(threshold), + ], + ); +} +const defaultScrollIntent = { + x: { + [Direction.Backward]: false, + [Direction.Forward]: false, + }, + y: { + [Direction.Backward]: false, + [Direction.Forward]: false, + }, +}; + +function useScrollIntent(_ref2) { + let { delta, disabled } = _ref2; + const previousDelta = usePrevious(delta); + return useLazyMemo( + (previousIntent) => { + if (disabled || !previousDelta || !previousIntent) { + // Reset scroll intent tracking when auto-scrolling is disabled + return defaultScrollIntent; + } + + const direction = { + x: Math.sign(delta.x - previousDelta.x), + y: Math.sign(delta.y - previousDelta.y), + }; // Keep track of the user intent to scroll in each direction for both axis + + return { + x: { + [Direction.Backward]: previousIntent.x[Direction.Backward] || direction.x === -1, + [Direction.Forward]: previousIntent.x[Direction.Forward] || direction.x === 1, + }, + y: { + [Direction.Backward]: previousIntent.y[Direction.Backward] || direction.y === -1, + [Direction.Forward]: previousIntent.y[Direction.Forward] || direction.y === 1, + }, + }; + }, + [disabled, delta, previousDelta], + ); +} + +function useCachedNode(draggableNodes, id) { + const draggableNode = id != null ? draggableNodes.get(id) : undefined; + const node = draggableNode ? draggableNode.node.current : null; + return useLazyMemo( + (cachedNode) => { + var _ref; + + if (id == null) { + return null; + } // In some cases, the draggable node can unmount while dragging + // This is the case for virtualized lists. In those situations, + // we fall back to the last known value for that node. + + return (_ref = node != null ? node : cachedNode) != null ? _ref : null; + }, + [node, id], + ); +} + +function useCombineActivators(sensors, getSyntheticHandler) { + return useMemo( + () => + sensors.reduce((accumulator, sensor) => { + const { sensor: Sensor } = sensor; + const sensorActivators = Sensor.activators.map((activator) => ({ + eventName: activator.eventName, + handler: getSyntheticHandler(activator.handler, sensor), + })); + return [...accumulator, ...sensorActivators]; + }, []), + [sensors, getSyntheticHandler], + ); +} + +var MeasuringStrategy; + +(function (MeasuringStrategy) { + MeasuringStrategy[(MeasuringStrategy["Always"] = 0)] = "Always"; + MeasuringStrategy[(MeasuringStrategy["BeforeDragging"] = 1)] = "BeforeDragging"; + MeasuringStrategy[(MeasuringStrategy["WhileDragging"] = 2)] = "WhileDragging"; +})(MeasuringStrategy || (MeasuringStrategy = {})); + +var MeasuringFrequency; + +(function (MeasuringFrequency) { + MeasuringFrequency["Optimized"] = "optimized"; +})(MeasuringFrequency || (MeasuringFrequency = {})); + +const defaultValue = /*#__PURE__*/ new Map(); +function useDroppableMeasuring(containers, _ref) { + let { dragging, dependencies, config } = _ref; + const [queue, setQueue] = useState(null); + const { frequency, measure, strategy } = config; + const containersRef = useRef$1(containers); + const disabled = isDisabled(); + const disabledRef = useLatestValue(disabled); + const measureDroppableContainers = useCallback( + function (ids) { + if (ids === void 0) { + ids = []; + } + + if (disabledRef.current) { + return; + } + + setQueue((value) => { + if (value === null) { + return ids; + } + + return value.concat(ids.filter((id) => !value.includes(id))); + }); + }, + [disabledRef], + ); + const timeoutId = useRef$1(null); + const droppableRects = useLazyMemo( + (previousValue) => { + if (disabled && !dragging) { + return defaultValue; + } + + if (!previousValue || previousValue === defaultValue || containersRef.current !== containers || queue != null) { + const map = new Map(); + + for (let container of containers) { + if (!container) { + continue; + } + + if (queue && queue.length > 0 && !queue.includes(container.id) && container.rect.current) { + // This container does not need to be re-measured + map.set(container.id, container.rect.current); + continue; + } + + const node = container.node.current; + const rect = node ? new Rect(measure(node), node) : null; + container.rect.current = rect; + + if (rect) { + map.set(container.id, rect); + } + } + + return map; + } + + return previousValue; + }, + [containers, queue, dragging, disabled, measure], + ); + useEffect(() => { + containersRef.current = containers; + }, [containers]); + useEffect( + () => { + if (disabled) { + return; + } + + measureDroppableContainers(); + }, // eslint-disable-next-line react-hooks/exhaustive-deps + [dragging, disabled], + ); + useEffect( + () => { + if (queue && queue.length > 0) { + setQueue(null); + } + }, //eslint-disable-next-line react-hooks/exhaustive-deps + [JSON.stringify(queue)], + ); + useEffect( + () => { + if (disabled || typeof frequency !== "number" || timeoutId.current !== null) { + return; + } + + timeoutId.current = setTimeout(() => { + measureDroppableContainers(); + timeoutId.current = null; + }, frequency); + }, // eslint-disable-next-line react-hooks/exhaustive-deps + [frequency, disabled, measureDroppableContainers, ...dependencies], + ); + return { + droppableRects, + measureDroppableContainers, + measuringScheduled: queue != null, + }; + + function isDisabled() { + switch (strategy) { + case MeasuringStrategy.Always: + return false; + + case MeasuringStrategy.BeforeDragging: + return dragging; + + default: + return !dragging; + } + } +} + +function useInitialValue(value, computeFn) { + return useLazyMemo( + (previousValue) => { + if (!value) { + return null; + } + + if (previousValue) { + return previousValue; + } + + return typeof computeFn === "function" ? computeFn(value) : value; + }, + [computeFn, value], + ); +} + +function useInitialRect(node, measure) { + return useInitialValue(node, measure); +} + +/** + * Returns a new MutationObserver instance. + * If `MutationObserver` is undefined in the execution environment, returns `undefined`. + */ + +function useMutationObserver(_ref) { + let { callback, disabled } = _ref; + const handleMutations = useEvent(callback); + const mutationObserver = useMemo(() => { + if (disabled || typeof window === "undefined" || typeof window.MutationObserver === "undefined") { + return undefined; + } + + const { MutationObserver } = window; + return new MutationObserver(handleMutations); + }, [handleMutations, disabled]); + useEffect(() => { + return () => (mutationObserver == null ? void 0 : mutationObserver.disconnect()); + }, [mutationObserver]); + return mutationObserver; +} + +/** + * Returns a new ResizeObserver instance bound to the `onResize` callback. + * If `ResizeObserver` is undefined in the execution environment, returns `undefined`. + */ + +function useResizeObserver(_ref) { + let { callback, disabled } = _ref; + const handleResize = useEvent(callback); + const resizeObserver = useMemo( + () => { + if (disabled || typeof window === "undefined" || typeof window.ResizeObserver === "undefined") { + return undefined; + } + + const { ResizeObserver } = window; + return new ResizeObserver(handleResize); + }, // eslint-disable-next-line react-hooks/exhaustive-deps + [disabled], + ); + useEffect(() => { + return () => (resizeObserver == null ? void 0 : resizeObserver.disconnect()); + }, [resizeObserver]); + return resizeObserver; +} + +function defaultMeasure(element) { + return new Rect(getClientRect(element), element); +} + +function useRect(element, measure, fallbackRect) { + if (measure === void 0) { + measure = defaultMeasure; + } + + const [rect, setRect] = useState(null); + + function measureRect() { + setRect((currentRect) => { + if (!element) { + return null; + } + + if (element.isConnected === false) { + var _ref; + + // Fall back to last rect we measured if the element is + // no longer connected to the DOM. + return (_ref = currentRect != null ? currentRect : fallbackRect) != null ? _ref : null; + } + + const newRect = measure(element); + + if (JSON.stringify(currentRect) === JSON.stringify(newRect)) { + return currentRect; + } + + return newRect; + }); + } + + const mutationObserver = useMutationObserver({ + callback(records) { + if (!element) { + return; + } + + for (const record of records) { + const { type, target } = record; + + if (type === "childList" && target instanceof HTMLElement && target.contains(element)) { + measureRect(); + break; + } + } + }, + }); + const resizeObserver = useResizeObserver({ + callback: measureRect, + }); + useIsomorphicLayoutEffect(() => { + measureRect(); + + if (element) { + resizeObserver == null ? void 0 : resizeObserver.observe(element); + mutationObserver == null ? void 0 : ( + mutationObserver.observe(document.body, { + childList: true, + subtree: true, + }) + ); + } else { + resizeObserver == null ? void 0 : resizeObserver.disconnect(); + mutationObserver == null ? void 0 : mutationObserver.disconnect(); + } + }, [element]); + return rect; +} + +function useRectDelta(rect) { + const initialRect = useInitialValue(rect); + return getRectDelta(rect, initialRect); +} + +const defaultValue$1 = []; +function useScrollableAncestors(node) { + const previousNode = useRef$1(node); + const ancestors = useLazyMemo( + (previousValue) => { + if (!node) { + return defaultValue$1; + } + + if (previousValue && previousValue !== defaultValue$1 && node && previousNode.current && node.parentNode === previousNode.current.parentNode) { + return previousValue; + } + + return getScrollableAncestors(node); + }, + [node], + ); + useEffect(() => { + previousNode.current = node; + }, [node]); + return ancestors; +} + +function useScrollOffsets(elements) { + const [scrollCoordinates, setScrollCoordinates] = useState(null); + const prevElements = useRef$1(elements); // To-do: Throttle the handleScroll callback + + const handleScroll = useCallback((event) => { + const scrollingElement = getScrollableElement(event.target); + + if (!scrollingElement) { + return; + } + + setScrollCoordinates((scrollCoordinates) => { + if (!scrollCoordinates) { + return null; + } + + scrollCoordinates.set(scrollingElement, getScrollCoordinates(scrollingElement)); + return new Map(scrollCoordinates); + }); + }, []); + useEffect(() => { + const previousElements = prevElements.current; + + if (elements !== previousElements) { + cleanup(previousElements); + const entries = elements + .map((element) => { + const scrollableElement = getScrollableElement(element); + + if (scrollableElement) { + scrollableElement.addEventListener("scroll", handleScroll, { + passive: true, + }); + return [scrollableElement, getScrollCoordinates(scrollableElement)]; + } + + return null; + }) + .filter((entry) => entry != null); + setScrollCoordinates(entries.length ? new Map(entries) : null); + prevElements.current = elements; + } + + return () => { + cleanup(elements); + cleanup(previousElements); + }; + + function cleanup(elements) { + elements.forEach((element) => { + const scrollableElement = getScrollableElement(element); + scrollableElement == null ? void 0 : scrollableElement.removeEventListener("scroll", handleScroll); + }); + } + }, [handleScroll, elements]); + return useMemo(() => { + if (elements.length) { + return scrollCoordinates ? Array.from(scrollCoordinates.values()).reduce((acc, coordinates) => add(acc, coordinates), defaultCoordinates) : getScrollOffsets(elements); + } + + return defaultCoordinates; + }, [elements, scrollCoordinates]); +} + +function useScrollOffsetsDelta(scrollOffsets, dependencies) { + if (dependencies === void 0) { + dependencies = []; + } + + const initialScrollOffsets = useRef$1(null); + useEffect( + () => { + initialScrollOffsets.current = null; + }, // eslint-disable-next-line react-hooks/exhaustive-deps + dependencies, + ); + useEffect(() => { + const hasScrollOffsets = scrollOffsets !== defaultCoordinates; + + if (hasScrollOffsets && !initialScrollOffsets.current) { + initialScrollOffsets.current = scrollOffsets; + } + + if (!hasScrollOffsets && initialScrollOffsets.current) { + initialScrollOffsets.current = null; + } + }, [scrollOffsets]); + return initialScrollOffsets.current ? subtract(scrollOffsets, initialScrollOffsets.current) : defaultCoordinates; +} + +function useSensorSetup(sensors) { + useEffect( + () => { + if (!canUseDOM) { + return; + } + + const teardownFns = sensors.map((_ref) => { + let { sensor } = _ref; + return sensor.setup == null ? void 0 : sensor.setup(); + }); + return () => { + for (const teardown of teardownFns) { + teardown == null ? void 0 : teardown(); + } + }; + }, // TO-DO: Sensors length could theoretically change which would not be a valid dependency + // eslint-disable-next-line react-hooks/exhaustive-deps + sensors.map((_ref2) => { + let { sensor } = _ref2; + return sensor; + }), + ); +} + +function useWindowRect(element) { + return useMemo(() => (element ? getWindowClientRect(element) : null), [element]); +} + +const defaultValue$2 = []; +function useRects(elements, measure) { + if (measure === void 0) { + measure = getClientRect; + } + + const [firstElement] = elements; + const windowRect = useWindowRect(firstElement ? getWindow(firstElement) : null); + const [rects, setRects] = useState(defaultValue$2); + + function measureRects() { + setRects(() => { + if (!elements.length) { + return defaultValue$2; + } + + return elements.map((element) => (isDocumentScrollingElement(element) ? windowRect : new Rect(measure(element), element))); + }); + } + + const resizeObserver = useResizeObserver({ + callback: measureRects, + }); + useIsomorphicLayoutEffect(() => { + resizeObserver == null ? void 0 : resizeObserver.disconnect(); + measureRects(); + elements.forEach((element) => (resizeObserver == null ? void 0 : resizeObserver.observe(element))); + }, [elements]); + return rects; +} + +function getMeasurableNode(node) { + if (!node) { + return null; + } + + if (node.children.length > 1) { + return node; + } + + const firstChild = node.children[0]; + return isHTMLElement(firstChild) ? firstChild : node; +} + +function useDragOverlayMeasuring(_ref) { + let { measure } = _ref; + const [rect, setRect] = useState(null); + const handleResize = useCallback( + (entries) => { + for (const { target } of entries) { + if (isHTMLElement(target)) { + setRect((rect) => { + const newRect = measure(target); + return rect ? { ...rect, width: newRect.width, height: newRect.height } : newRect; + }); + break; + } + } + }, + [measure], + ); + const resizeObserver = useResizeObserver({ + callback: handleResize, + }); + const handleNodeChange = useCallback( + (element) => { + const node = getMeasurableNode(element); + resizeObserver == null ? void 0 : resizeObserver.disconnect(); + + if (node) { + resizeObserver == null ? void 0 : resizeObserver.observe(node); + } + + setRect(node ? measure(node) : null); + }, + [measure, resizeObserver], + ); + const [nodeRef, setRef] = useNodeRef(handleNodeChange); + return useMemo( + () => ({ + nodeRef, + rect, + setRef, + }), + [rect, nodeRef, setRef], + ); +} + +const defaultSensors = [ + { + sensor: PointerSensor, + options: {}, + }, + { + sensor: KeyboardSensor, + options: {}, + }, +]; +const defaultData = { + current: {}, +}; +const defaultMeasuringConfiguration = { + draggable: { + measure: getTransformAgnosticClientRect, + }, + droppable: { + measure: getTransformAgnosticClientRect, + strategy: MeasuringStrategy.WhileDragging, + frequency: MeasuringFrequency.Optimized, + }, + dragOverlay: { + measure: getClientRect, + }, +}; + +class DroppableContainersMap extends Map { + get(id) { + var _super$get; + + return ( + id != null ? + (_super$get = super.get(id)) != null ? + _super$get + : undefined + : undefined + ); + } + + toArray() { + return Array.from(this.values()); + } + + getEnabled() { + return this.toArray().filter((_ref) => { + let { disabled } = _ref; + return !disabled; + }); + } + + getNodeFor(id) { + var _this$get$node$curren, _this$get; + + return (_this$get$node$curren = (_this$get = this.get(id)) == null ? void 0 : _this$get.node.current) != null ? _this$get$node$curren : undefined; + } +} + +const defaultPublicContext = { + activatorEvent: null, + active: null, + activeNode: null, + activeNodeRect: null, + collisions: null, + containerNodeRect: null, + draggableNodes: /*#__PURE__*/ new Map(), + droppableRects: /*#__PURE__*/ new Map(), + droppableContainers: /*#__PURE__*/ new DroppableContainersMap(), + over: null, + dragOverlay: { + nodeRef: { + current: null, + }, + rect: null, + setRef: noop, + }, + scrollableAncestors: [], + scrollableAncestorRects: [], + measuringConfiguration: defaultMeasuringConfiguration, + measureDroppableContainers: noop, + windowRect: null, + measuringScheduled: false, +}; +const defaultInternalContext = { + activatorEvent: null, + activators: [], + active: null, + activeNodeRect: null, + ariaDescribedById: { + draggable: "", + }, + dispatch: noop, + draggableNodes: /*#__PURE__*/ new Map(), + over: null, + measureDroppableContainers: noop, +}; +const InternalContext = /*#__PURE__*/ createContext$1(defaultInternalContext); +const PublicContext = /*#__PURE__*/ createContext$1(defaultPublicContext); + +function getInitialState() { + return { + draggable: { + active: null, + initialCoordinates: { + x: 0, + y: 0, + }, + nodes: new Map(), + translate: { + x: 0, + y: 0, + }, + }, + droppable: { + containers: new DroppableContainersMap(), + }, + }; +} +function reducer(state, action) { + switch (action.type) { + case Action.DragStart: + return { ...state, draggable: { ...state.draggable, initialCoordinates: action.initialCoordinates, active: action.active } }; + + case Action.DragMove: + if (state.draggable.active == null) { + return state; + } + + return { + ...state, + draggable: { + ...state.draggable, + translate: { + x: action.coordinates.x - state.draggable.initialCoordinates.x, + y: action.coordinates.y - state.draggable.initialCoordinates.y, + }, + }, + }; + + case Action.DragEnd: + case Action.DragCancel: + return { + ...state, + draggable: { + ...state.draggable, + active: null, + initialCoordinates: { + x: 0, + y: 0, + }, + translate: { + x: 0, + y: 0, + }, + }, + }; + + case Action.RegisterDroppable: { + const { element } = action; + const { id } = element; + const containers = new DroppableContainersMap(state.droppable.containers); + containers.set(id, element); + return { ...state, droppable: { ...state.droppable, containers } }; + } + + case Action.SetDroppableDisabled: { + const { id, key, disabled } = action; + const element = state.droppable.containers.get(id); + + if (!element || key !== element.key) { + return state; + } + + const containers = new DroppableContainersMap(state.droppable.containers); + containers.set(id, { ...element, disabled }); + return { ...state, droppable: { ...state.droppable, containers } }; + } + + case Action.UnregisterDroppable: { + const { id, key } = action; + const element = state.droppable.containers.get(id); + + if (!element || key !== element.key) { + return state; + } + + const containers = new DroppableContainersMap(state.droppable.containers); + containers.delete(id); + return { ...state, droppable: { ...state.droppable, containers } }; + } + + default: { + return state; + } + } +} + +function RestoreFocus(_ref) { + let { disabled } = _ref; + const { active, activatorEvent, draggableNodes } = useContext$1(InternalContext); + const previousActivatorEvent = usePrevious(activatorEvent); + const previousActiveId = usePrevious(active == null ? void 0 : active.id); // Restore keyboard focus on the activator node + + useEffect(() => { + if (disabled) { + return; + } + + if (!activatorEvent && previousActivatorEvent && previousActiveId != null) { + if (!isKeyboardEvent(previousActivatorEvent)) { + return; + } + + if (document.activeElement === previousActivatorEvent.target) { + // No need to restore focus + return; + } + + const draggableNode = draggableNodes.get(previousActiveId); + + if (!draggableNode) { + return; + } + + const { activatorNode, node } = draggableNode; + + if (!activatorNode.current && !node.current) { + return; + } + + requestAnimationFrame(() => { + for (const element of [activatorNode.current, node.current]) { + if (!element) { + continue; + } + + const focusableNode = findFirstFocusableNode(element); + + if (focusableNode) { + focusableNode.focus(); + break; + } + } + }); + } + }, [activatorEvent, disabled, draggableNodes, previousActiveId, previousActivatorEvent]); + return null; +} + +function applyModifiers(modifiers, _ref) { + let { transform, ...args } = _ref; + return modifiers != null && modifiers.length ? + modifiers.reduce((accumulator, modifier) => { + return modifier({ + transform: accumulator, + ...args, + }); + }, transform) + : transform; +} + +function useMeasuringConfiguration(config) { + return useMemo( + () => ({ + draggable: { ...defaultMeasuringConfiguration.draggable, ...(config == null ? void 0 : config.draggable) }, + droppable: { ...defaultMeasuringConfiguration.droppable, ...(config == null ? void 0 : config.droppable) }, + dragOverlay: { ...defaultMeasuringConfiguration.dragOverlay, ...(config == null ? void 0 : config.dragOverlay) }, + }), // eslint-disable-next-line react-hooks/exhaustive-deps + [config == null ? void 0 : config.draggable, config == null ? void 0 : config.droppable, config == null ? void 0 : config.dragOverlay], + ); +} + +function useLayoutShiftScrollCompensation(_ref) { + let { activeNode, measure, initialRect, config = true } = _ref; + const initialized = useRef$1(false); + const { x, y } = + typeof config === "boolean" ? + { + x: config, + y: config, + } + : config; + useIsomorphicLayoutEffect(() => { + const disabled = !x && !y; + + if (disabled || !activeNode) { + initialized.current = false; + return; + } + + if (initialized.current || !initialRect) { + // Return early if layout shift scroll compensation was already attempted + // or if there is no initialRect to compare to. + return; + } // Get the most up to date node ref for the active draggable + + const node = activeNode == null ? void 0 : activeNode.node.current; + + if (!node || node.isConnected === false) { + // Return early if there is no attached node ref or if the node is + // disconnected from the document. + return; + } + + const rect = measure(node); + const rectDelta = getRectDelta(rect, initialRect); + + if (!x) { + rectDelta.x = 0; + } + + if (!y) { + rectDelta.y = 0; + } // Only perform layout shift scroll compensation once + + initialized.current = true; + + if (Math.abs(rectDelta.x) > 0 || Math.abs(rectDelta.y) > 0) { + const firstScrollableAncestor = getFirstScrollableAncestor(node); + + if (firstScrollableAncestor) { + firstScrollableAncestor.scrollBy({ + top: rectDelta.y, + left: rectDelta.x, + }); + } + } + }, [activeNode, x, y, initialRect, measure]); +} + +const ActiveDraggableContext = /*#__PURE__*/ createContext$1({ ...defaultCoordinates, scaleX: 1, scaleY: 1 }); +var Status; + +(function (Status) { + Status[(Status["Uninitialized"] = 0)] = "Uninitialized"; + Status[(Status["Initializing"] = 1)] = "Initializing"; + Status[(Status["Initialized"] = 2)] = "Initialized"; +})(Status || (Status = {})); + +const DndContext = /*#__PURE__*/ memo$1(function DndContext(_ref) { + var _sensorContext$curren, _dragOverlay$nodeRef$, _dragOverlay$rect, _over$rect; + + let { id, accessibility, autoScroll = true, children, sensors = defaultSensors, collisionDetection = rectIntersection, measuring, modifiers, ...props } = _ref; + const store = useReducer(reducer, undefined, getInitialState); + const [state, dispatch] = store; + const [dispatchMonitorEvent, registerMonitorListener] = useDndMonitorProvider(); + const [status, setStatus] = useState(Status.Uninitialized); + const isInitialized = status === Status.Initialized; + const { + draggable: { active: activeId, nodes: draggableNodes, translate }, + droppable: { containers: droppableContainers }, + } = state; + const node = activeId != null ? draggableNodes.get(activeId) : null; + const activeRects = useRef$1({ + initial: null, + translated: null, + }); + const active = useMemo(() => { + var _node$data; + + return activeId != null ? + { + id: activeId, + // It's possible for the active node to unmount while dragging + data: (_node$data = node == null ? void 0 : node.data) != null ? _node$data : defaultData, + rect: activeRects, + } + : null; + }, [activeId, node]); + const activeRef = useRef$1(null); + const [activeSensor, setActiveSensor] = useState(null); + const [activatorEvent, setActivatorEvent] = useState(null); + const latestProps = useLatestValue(props, Object.values(props)); + const draggableDescribedById = useUniqueId("DndDescribedBy", id); + const enabledDroppableContainers = useMemo(() => droppableContainers.getEnabled(), [droppableContainers]); + const measuringConfiguration = useMeasuringConfiguration(measuring); + const { droppableRects, measureDroppableContainers, measuringScheduled } = useDroppableMeasuring(enabledDroppableContainers, { + dragging: isInitialized, + dependencies: [translate.x, translate.y], + config: measuringConfiguration.droppable, + }); + const activeNode = useCachedNode(draggableNodes, activeId); + const activationCoordinates = useMemo(() => (activatorEvent ? getEventCoordinates(activatorEvent) : null), [activatorEvent]); + const autoScrollOptions = getAutoScrollerOptions(); + const initialActiveNodeRect = useInitialRect(activeNode, measuringConfiguration.draggable.measure); + useLayoutShiftScrollCompensation({ + activeNode: activeId != null ? draggableNodes.get(activeId) : null, + config: autoScrollOptions.layoutShiftCompensation, + initialRect: initialActiveNodeRect, + measure: measuringConfiguration.draggable.measure, + }); + const activeNodeRect = useRect(activeNode, measuringConfiguration.draggable.measure, initialActiveNodeRect); + const containerNodeRect = useRect(activeNode ? activeNode.parentElement : null); + const sensorContext = useRef$1({ + activatorEvent: null, + active: null, + activeNode, + collisionRect: null, + collisions: null, + droppableRects, + draggableNodes, + draggingNode: null, + draggingNodeRect: null, + droppableContainers, + over: null, + scrollableAncestors: [], + scrollAdjustedTranslate: null, + }); + const overNode = droppableContainers.getNodeFor((_sensorContext$curren = sensorContext.current.over) == null ? void 0 : _sensorContext$curren.id); + const dragOverlay = useDragOverlayMeasuring({ + measure: measuringConfiguration.dragOverlay.measure, + }); // Use the rect of the drag overlay if it is mounted + + const draggingNode = (_dragOverlay$nodeRef$ = dragOverlay.nodeRef.current) != null ? _dragOverlay$nodeRef$ : activeNode; + const draggingNodeRect = + isInitialized ? + (_dragOverlay$rect = dragOverlay.rect) != null ? + _dragOverlay$rect + : activeNodeRect + : null; + const usesDragOverlay = Boolean(dragOverlay.nodeRef.current && dragOverlay.rect); // The delta between the previous and new position of the draggable node + // is only relevant when there is no drag overlay + + const nodeRectDelta = useRectDelta(usesDragOverlay ? null : activeNodeRect); // Get the window rect of the dragging node + + const windowRect = useWindowRect(draggingNode ? getWindow(draggingNode) : null); // Get scrollable ancestors of the dragging node + + const scrollableAncestors = useScrollableAncestors( + isInitialized ? + overNode != null ? + overNode + : activeNode + : null, + ); + const scrollableAncestorRects = useRects(scrollableAncestors); // Apply modifiers + + const modifiedTranslate = applyModifiers(modifiers, { + transform: { + x: translate.x - nodeRectDelta.x, + y: translate.y - nodeRectDelta.y, + scaleX: 1, + scaleY: 1, + }, + activatorEvent, + active, + activeNodeRect, + containerNodeRect, + draggingNodeRect, + over: sensorContext.current.over, + overlayNodeRect: dragOverlay.rect, + scrollableAncestors, + scrollableAncestorRects, + windowRect, + }); + const pointerCoordinates = activationCoordinates ? add(activationCoordinates, translate) : null; + const scrollOffsets = useScrollOffsets(scrollableAncestors); // Represents the scroll delta since dragging was initiated + + const scrollAdjustment = useScrollOffsetsDelta(scrollOffsets); // Represents the scroll delta since the last time the active node rect was measured + + const activeNodeScrollDelta = useScrollOffsetsDelta(scrollOffsets, [activeNodeRect]); + const scrollAdjustedTranslate = add(modifiedTranslate, scrollAdjustment); + const collisionRect = draggingNodeRect ? getAdjustedRect(draggingNodeRect, modifiedTranslate) : null; + const collisions = + active && collisionRect ? + collisionDetection({ + active, + collisionRect, + droppableRects, + droppableContainers: enabledDroppableContainers, + pointerCoordinates, + }) + : null; + const overId = getFirstCollision(collisions, "id"); + const [over, setOver] = useState(null); // When there is no drag overlay used, we need to account for the + // window scroll delta + + const appliedTranslate = usesDragOverlay ? modifiedTranslate : add(modifiedTranslate, activeNodeScrollDelta); + const transform = adjustScale(appliedTranslate, (_over$rect = over == null ? void 0 : over.rect) != null ? _over$rect : null, activeNodeRect); + const activeSensorRef = useRef$1(null); + const instantiateSensor = useCallback( + (event, _ref2) => { + let { sensor: Sensor, options } = _ref2; + + if (activeRef.current == null) { + return; + } + + const activeNode = draggableNodes.get(activeRef.current); + + if (!activeNode) { + return; + } + + const activatorEvent = event.nativeEvent; + const sensorInstance = new Sensor({ + active: activeRef.current, + activeNode, + event: activatorEvent, + options, + // Sensors need to be instantiated with refs for arguments that change over time + // otherwise they are frozen in time with the stale arguments + context: sensorContext, + + onAbort(id) { + const draggableNode = draggableNodes.get(id); + + if (!draggableNode) { + return; + } + + const { onDragAbort } = latestProps.current; + const event = { + id, + }; + onDragAbort == null ? void 0 : onDragAbort(event); + dispatchMonitorEvent({ + type: "onDragAbort", + event, + }); + }, + + onPending(id, constraint, initialCoordinates, offset) { + const draggableNode = draggableNodes.get(id); + + if (!draggableNode) { + return; + } + + const { onDragPending } = latestProps.current; + const event = { + id, + constraint, + initialCoordinates, + offset, + }; + onDragPending == null ? void 0 : onDragPending(event); + dispatchMonitorEvent({ + type: "onDragPending", + event, + }); + }, + + onStart(initialCoordinates) { + const id = activeRef.current; + + if (id == null) { + return; + } + + const draggableNode = draggableNodes.get(id); + + if (!draggableNode) { + return; + } + + const { onDragStart } = latestProps.current; + const event = { + activatorEvent, + active: { + id, + data: draggableNode.data, + rect: activeRects, + }, + }; + unstable_batchedUpdates(() => { + onDragStart == null ? void 0 : onDragStart(event); + setStatus(Status.Initializing); + dispatch({ + type: Action.DragStart, + initialCoordinates, + active: id, + }); + dispatchMonitorEvent({ + type: "onDragStart", + event, + }); + setActiveSensor(activeSensorRef.current); + setActivatorEvent(activatorEvent); + }); + }, + + onMove(coordinates) { + dispatch({ + type: Action.DragMove, + coordinates, + }); + }, + + onEnd: createHandler(Action.DragEnd), + onCancel: createHandler(Action.DragCancel), + }); + activeSensorRef.current = sensorInstance; + + function createHandler(type) { + return async function handler() { + const { active, collisions, over, scrollAdjustedTranslate } = sensorContext.current; + let event = null; + + if (active && scrollAdjustedTranslate) { + const { cancelDrop } = latestProps.current; + event = { + activatorEvent, + active: active, + collisions, + delta: scrollAdjustedTranslate, + over, + }; + + if (type === Action.DragEnd && typeof cancelDrop === "function") { + const shouldCancel = await Promise.resolve(cancelDrop(event)); + + if (shouldCancel) { + type = Action.DragCancel; + } + } + } + + activeRef.current = null; + unstable_batchedUpdates(() => { + dispatch({ + type, + }); + setStatus(Status.Uninitialized); + setOver(null); + setActiveSensor(null); + setActivatorEvent(null); + activeSensorRef.current = null; + const eventName = type === Action.DragEnd ? "onDragEnd" : "onDragCancel"; + + if (event) { + const handler = latestProps.current[eventName]; + handler == null ? void 0 : handler(event); + dispatchMonitorEvent({ + type: eventName, + event, + }); + } + }); + }; + } + }, // eslint-disable-next-line react-hooks/exhaustive-deps + [draggableNodes], + ); + const bindActivatorToSensorInstantiator = useCallback( + (handler, sensor) => { + return (event, active) => { + const nativeEvent = event.nativeEvent; + const activeDraggableNode = draggableNodes.get(active); + + if ( + // Another sensor is already instantiating + activeRef.current !== null || // No active draggable + !activeDraggableNode || // Event has already been captured + nativeEvent.dndKit || + nativeEvent.defaultPrevented + ) { + return; + } + + const activationContext = { + active: activeDraggableNode, + }; + const shouldActivate = handler(event, sensor.options, activationContext); + + if (shouldActivate === true) { + nativeEvent.dndKit = { + capturedBy: sensor.sensor, + }; + activeRef.current = active; + instantiateSensor(event, sensor); + } + }; + }, + [draggableNodes, instantiateSensor], + ); + const activators = useCombineActivators(sensors, bindActivatorToSensorInstantiator); + useSensorSetup(sensors); + useIsomorphicLayoutEffect(() => { + if (activeNodeRect && status === Status.Initializing) { + setStatus(Status.Initialized); + } + }, [activeNodeRect, status]); + useEffect( + () => { + const { onDragMove } = latestProps.current; + const { active, activatorEvent, collisions, over } = sensorContext.current; + + if (!active || !activatorEvent) { + return; + } + + const event = { + active, + activatorEvent, + collisions, + delta: { + x: scrollAdjustedTranslate.x, + y: scrollAdjustedTranslate.y, + }, + over, + }; + unstable_batchedUpdates(() => { + onDragMove == null ? void 0 : onDragMove(event); + dispatchMonitorEvent({ + type: "onDragMove", + event, + }); + }); + }, // eslint-disable-next-line react-hooks/exhaustive-deps + [scrollAdjustedTranslate.x, scrollAdjustedTranslate.y], + ); + useEffect( + () => { + const { active, activatorEvent, collisions, droppableContainers, scrollAdjustedTranslate } = sensorContext.current; + + if (!active || activeRef.current == null || !activatorEvent || !scrollAdjustedTranslate) { + return; + } + + const { onDragOver } = latestProps.current; + const overContainer = droppableContainers.get(overId); + const over = + overContainer && overContainer.rect.current ? + { + id: overContainer.id, + rect: overContainer.rect.current, + data: overContainer.data, + disabled: overContainer.disabled, + } + : null; + const event = { + active, + activatorEvent, + collisions, + delta: { + x: scrollAdjustedTranslate.x, + y: scrollAdjustedTranslate.y, + }, + over, + }; + unstable_batchedUpdates(() => { + setOver(over); + onDragOver == null ? void 0 : onDragOver(event); + dispatchMonitorEvent({ + type: "onDragOver", + event, + }); + }); + }, // eslint-disable-next-line react-hooks/exhaustive-deps + [overId], + ); + useIsomorphicLayoutEffect(() => { + sensorContext.current = { + activatorEvent, + active, + activeNode, + collisionRect, + collisions, + droppableRects, + draggableNodes, + draggingNode, + draggingNodeRect, + droppableContainers, + over, + scrollableAncestors, + scrollAdjustedTranslate, + }; + activeRects.current = { + initial: draggingNodeRect, + translated: collisionRect, + }; + }, [active, activeNode, collisions, collisionRect, draggableNodes, draggingNode, draggingNodeRect, droppableRects, droppableContainers, over, scrollableAncestors, scrollAdjustedTranslate]); + useAutoScroller({ ...autoScrollOptions, delta: translate, draggingRect: collisionRect, pointerCoordinates, scrollableAncestors, scrollableAncestorRects }); + const publicContext = useMemo(() => { + const context = { + active, + activeNode, + activeNodeRect, + activatorEvent, + collisions, + containerNodeRect, + dragOverlay, + draggableNodes, + droppableContainers, + droppableRects, + over, + measureDroppableContainers, + scrollableAncestors, + scrollableAncestorRects, + measuringConfiguration, + measuringScheduled, + windowRect, + }; + return context; + }, [ + active, + activeNode, + activeNodeRect, + activatorEvent, + collisions, + containerNodeRect, + dragOverlay, + draggableNodes, + droppableContainers, + droppableRects, + over, + measureDroppableContainers, + scrollableAncestors, + scrollableAncestorRects, + measuringConfiguration, + measuringScheduled, + windowRect, + ]); + const internalContext = useMemo(() => { + const context = { + activatorEvent, + activators, + active, + activeNodeRect, + ariaDescribedById: { + draggable: draggableDescribedById, + }, + dispatch, + draggableNodes, + over, + measureDroppableContainers, + }; + return context; + }, [activatorEvent, activators, active, activeNodeRect, dispatch, draggableDescribedById, draggableNodes, over, measureDroppableContainers]); + return React$2.createElement( + DndMonitorContext.Provider, + { + value: registerMonitorListener, + }, + React$2.createElement( + InternalContext.Provider, + { + value: internalContext, + }, + React$2.createElement( + PublicContext.Provider, + { + value: publicContext, + }, + React$2.createElement( + ActiveDraggableContext.Provider, + { + value: transform, + }, + children, + ), + ), + React$2.createElement(RestoreFocus, { + disabled: (accessibility == null ? void 0 : accessibility.restoreFocus) === false, + }), + ), + React$2.createElement(Accessibility, { ...accessibility, hiddenTextDescribedById: draggableDescribedById }), + ); + + function getAutoScrollerOptions() { + const activeSensorDisablesAutoscroll = (activeSensor == null ? void 0 : activeSensor.autoScrollEnabled) === false; + const autoScrollGloballyDisabled = typeof autoScroll === "object" ? autoScroll.enabled === false : autoScroll === false; + const enabled = isInitialized && !activeSensorDisablesAutoscroll && !autoScrollGloballyDisabled; + + if (typeof autoScroll === "object") { + return { ...autoScroll, enabled }; + } + + return { + enabled, + }; + } +}); + +function useDndContext() { + return useContext$1(PublicContext); +} + +function AnimationManager(_ref) { + let { animation, children } = _ref; + const [clonedChildren, setClonedChildren] = useState(null); + const [element, setElement] = useState(null); + const previousChildren = usePrevious(children); + + if (!children && !clonedChildren && previousChildren) { + setClonedChildren(previousChildren); + } + + useIsomorphicLayoutEffect(() => { + if (!element) { + return; + } + + const key = clonedChildren == null ? void 0 : clonedChildren.key; + const id = clonedChildren == null ? void 0 : clonedChildren.props.id; + + if (key == null || id == null) { + setClonedChildren(null); + return; + } + + Promise.resolve(animation(id, element)).then(() => { + setClonedChildren(null); + }); + }, [animation, clonedChildren, element]); + return React$2.createElement( + React$2.Fragment, + null, + children, + clonedChildren ? + cloneElement(clonedChildren, { + ref: setElement, + }) + : null, + ); +} + +const defaultTransform = { + x: 0, + y: 0, + scaleX: 1, + scaleY: 1, +}; +function NullifiedContextProvider(_ref) { + let { children } = _ref; + return React$2.createElement( + InternalContext.Provider, + { + value: defaultInternalContext, + }, + React$2.createElement( + ActiveDraggableContext.Provider, + { + value: defaultTransform, + }, + children, + ), + ); +} + +const baseStyles = { + position: "fixed", + touchAction: "none", +}; + +const defaultTransition = (activatorEvent) => { + const isKeyboardActivator = isKeyboardEvent(activatorEvent); + return isKeyboardActivator ? "transform 250ms ease" : undefined; +}; + +const PositionedOverlay = /*#__PURE__*/ forwardRef((_ref, ref) => { + let { as, activatorEvent, adjustScale, children, className, rect, style, transform, transition = defaultTransition } = _ref; + + if (!rect) { + return null; + } + + const scaleAdjustedTransform = adjustScale ? transform : { ...transform, scaleX: 1, scaleY: 1 }; + const styles = { + ...baseStyles, + width: rect.width, + height: rect.height, + top: rect.top, + left: rect.left, + transform: CSS.Transform.toString(scaleAdjustedTransform), + transformOrigin: adjustScale && activatorEvent ? getRelativeTransformOrigin(activatorEvent, rect) : undefined, + transition: typeof transition === "function" ? transition(activatorEvent) : transition, + ...style, + }; + return React$2.createElement( + as, + { + className, + style: styles, + ref, + }, + children, + ); +}); + +const defaultDropAnimationSideEffects = (options) => (_ref) => { + let { active, dragOverlay } = _ref; + const originalStyles = {}; + const { styles, className } = options; + + if (styles != null && styles.active) { + for (const [key, value] of Object.entries(styles.active)) { + if (value === undefined) { + continue; + } + + originalStyles[key] = active.node.style.getPropertyValue(key); + active.node.style.setProperty(key, value); + } + } + + if (styles != null && styles.dragOverlay) { + for (const [key, value] of Object.entries(styles.dragOverlay)) { + if (value === undefined) { + continue; + } + + dragOverlay.node.style.setProperty(key, value); + } + } + + if (className != null && className.active) { + active.node.classList.add(className.active); + } + + if (className != null && className.dragOverlay) { + dragOverlay.node.classList.add(className.dragOverlay); + } + + return function cleanup() { + for (const [key, value] of Object.entries(originalStyles)) { + active.node.style.setProperty(key, value); + } + + if (className != null && className.active) { + active.node.classList.remove(className.active); + } + }; +}; + +const defaultKeyframeResolver = (_ref2) => { + let { + transform: { initial, final }, + } = _ref2; + return [ + { + transform: CSS.Transform.toString(initial), + }, + { + transform: CSS.Transform.toString(final), + }, + ]; +}; + +const defaultDropAnimationConfiguration = { + duration: 250, + easing: "ease", + keyframes: defaultKeyframeResolver, + sideEffects: /*#__PURE__*/ defaultDropAnimationSideEffects({ + styles: { + active: { + opacity: "0", + }, + }, + }), +}; +function useDropAnimation(_ref3) { + let { config, draggableNodes, droppableContainers, measuringConfiguration } = _ref3; + return useEvent((id, node) => { + if (config === null) { + return; + } + + const activeDraggable = draggableNodes.get(id); + + if (!activeDraggable) { + return; + } + + const activeNode = activeDraggable.node.current; + + if (!activeNode) { + return; + } + + const measurableNode = getMeasurableNode(node); + + if (!measurableNode) { + return; + } + + const { transform } = getWindow(node).getComputedStyle(node); + const parsedTransform = parseTransform(transform); + + if (!parsedTransform) { + return; + } + + const animation = typeof config === "function" ? config : createDefaultDropAnimation(config); + scrollIntoViewIfNeeded(activeNode, measuringConfiguration.draggable.measure); + return animation({ + active: { + id, + data: activeDraggable.data, + node: activeNode, + rect: measuringConfiguration.draggable.measure(activeNode), + }, + draggableNodes, + dragOverlay: { + node, + rect: measuringConfiguration.dragOverlay.measure(measurableNode), + }, + droppableContainers, + measuringConfiguration, + transform: parsedTransform, + }); + }); +} + +function createDefaultDropAnimation(options) { + const { duration, easing, sideEffects, keyframes } = { ...defaultDropAnimationConfiguration, ...options }; + return (_ref4) => { + let { active, dragOverlay, transform, ...rest } = _ref4; + + if (!duration) { + // Do not animate if animation duration is zero. + return; + } + + const delta = { + x: dragOverlay.rect.left - active.rect.left, + y: dragOverlay.rect.top - active.rect.top, + }; + const scale = { + scaleX: transform.scaleX !== 1 ? (active.rect.width * transform.scaleX) / dragOverlay.rect.width : 1, + scaleY: transform.scaleY !== 1 ? (active.rect.height * transform.scaleY) / dragOverlay.rect.height : 1, + }; + const finalTransform = { + x: transform.x - delta.x, + y: transform.y - delta.y, + ...scale, + }; + const animationKeyframes = keyframes({ + ...rest, + active, + dragOverlay, + transform: { + initial: transform, + final: finalTransform, + }, + }); + const [firstKeyframe] = animationKeyframes; + const lastKeyframe = animationKeyframes[animationKeyframes.length - 1]; + + if (JSON.stringify(firstKeyframe) === JSON.stringify(lastKeyframe)) { + // The start and end keyframes are the same, infer that there is no animation needed. + return; + } + + const cleanup = + sideEffects == null ? void 0 : ( + sideEffects({ + active, + dragOverlay, + ...rest, + }) + ); + const animation = dragOverlay.node.animate(animationKeyframes, { + duration, + easing, + fill: "forwards", + }); + return new Promise((resolve) => { + animation.onfinish = () => { + cleanup == null ? void 0 : cleanup(); + resolve(); + }; + }); + }; +} + +let key = 0; +function useKey(id) { + return useMemo(() => { + if (id == null) { + return; + } + + key++; + return key; + }, [id]); +} + +const DragOverlay = /*#__PURE__*/ React$2.memo((_ref) => { + let { adjustScale = false, children, dropAnimation: dropAnimationConfig, style, transition, modifiers, wrapperElement = "div", className, zIndex = 999 } = _ref; + const { + activatorEvent, + active, + activeNodeRect, + containerNodeRect, + draggableNodes, + droppableContainers, + dragOverlay, + over, + measuringConfiguration, + scrollableAncestors, + scrollableAncestorRects, + windowRect, + } = useDndContext(); + const transform = useContext$1(ActiveDraggableContext); + const key = useKey(active == null ? void 0 : active.id); + const modifiedTransform = applyModifiers(modifiers, { + activatorEvent, + active, + activeNodeRect, + containerNodeRect, + draggingNodeRect: dragOverlay.rect, + over, + overlayNodeRect: dragOverlay.rect, + scrollableAncestors, + scrollableAncestorRects, + transform, + windowRect, + }); + const initialRect = useInitialValue(activeNodeRect); + const dropAnimation = useDropAnimation({ + config: dropAnimationConfig, + draggableNodes, + droppableContainers, + measuringConfiguration, + }); // We need to wait for the active node to be measured before connecting the drag overlay ref + // otherwise collisions can be computed against a mispositioned drag overlay + + const ref = initialRect ? dragOverlay.setRef : undefined; + return React$2.createElement( + NullifiedContextProvider, + null, + React$2.createElement( + AnimationManager, + { + animation: dropAnimation, + }, + active && key ? + React$2.createElement( + PositionedOverlay, + { + key: key, + id: active.id, + ref: ref, + as: wrapperElement, + activatorEvent: activatorEvent, + adjustScale: adjustScale, + className: className, + transition: transition, + rect: initialRect, + style: { + zIndex, + ...style, + }, + transform: modifiedTransform, + }, + children, + ) + : null, + ), + ); +}); + +/** + * table-core + * + * Copyright (c) TanStack + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */ +function functionalUpdate(updater, input) { + return typeof updater === "function" ? updater(input) : updater; +} +function makeStateUpdater(key, instance) { + return (updater) => { + instance.setState((old) => { + return { + ...old, + [key]: functionalUpdate(updater, old[key]), + }; + }); + }; +} +function isFunction(d) { + return d instanceof Function; +} +function isNumberArray(d) { + return Array.isArray(d) && d.every((val) => typeof val === "number"); +} +function flattenBy(arr, getChildren) { + const flat = []; + const recurse = (subArr) => { + subArr.forEach((item) => { + flat.push(item); + const children = getChildren(item); + if (children != null && children.length) { + recurse(children); + } + }); + }; + recurse(arr); + return flat; +} +function memo(getDeps, fn, opts) { + let deps = []; + let result; + return (depArgs) => { + let depTime; + if (opts.key && opts.debug) depTime = Date.now(); + const newDeps = getDeps(depArgs); + const depsChanged = newDeps.length !== deps.length || newDeps.some((dep, index) => deps[index] !== dep); + if (!depsChanged) { + return result; + } + deps = newDeps; + let resultTime; + if (opts.key && opts.debug) resultTime = Date.now(); + result = fn(...newDeps); + opts == null || opts.onChange == null || opts.onChange(result); + if (opts.key && opts.debug) { + if (opts != null && opts.debug()) { + const depEndTime = Math.round((Date.now() - depTime) * 100) / 100; + const resultEndTime = Math.round((Date.now() - resultTime) * 100) / 100; + const resultFpsPercentage = resultEndTime / 16; + const pad = (str, num) => { + str = String(str); + while (str.length < num) { + str = " " + str; + } + return str; + }; + console.info( + `%c⏱ ${pad(resultEndTime, 5)} /${pad(depEndTime, 5)} ms`, + ` + font-size: .6rem; + font-weight: bold; + color: hsl(${Math.max(0, Math.min(120 - 120 * resultFpsPercentage, 120))}deg 100% 31%);`, + opts == null ? void 0 : opts.key, + ); + } + } + return result; + }; +} +function getMemoOptions(tableOptions, debugLevel, key, onChange) { + return { + debug: () => { + var _tableOptions$debugAl; + return (_tableOptions$debugAl = tableOptions == null ? void 0 : tableOptions.debugAll) != null ? _tableOptions$debugAl : tableOptions[debugLevel]; + }, + key: false, + onChange, + }; +} +function createCell(table, row, column, columnId) { + const getRenderValue = () => { + var _cell$getValue; + return (_cell$getValue = cell.getValue()) != null ? _cell$getValue : table.options.renderFallbackValue; + }; + const cell = { + id: `${row.id}_${column.id}`, + row, + column, + getValue: () => row.getValue(columnId), + renderValue: getRenderValue, + getContext: memo( + () => [table, column, row, cell], + (table2, column2, row2, cell2) => ({ + table: table2, + column: column2, + row: row2, + cell: cell2, + getValue: cell2.getValue, + renderValue: cell2.renderValue, + }), + getMemoOptions(table.options, "debugCells"), + ), + }; + table._features.forEach((feature) => { + feature.createCell == null || feature.createCell(cell, column, row, table); + }, {}); + return cell; +} +function createColumn(table, columnDef, depth, parent) { + var _ref, _resolvedColumnDef$id; + const defaultColumn = table._getDefaultColumnDef(); + const resolvedColumnDef = { + ...defaultColumn, + ...columnDef, + }; + const accessorKey = resolvedColumnDef.accessorKey; + let id = + ( + (_ref = + (_resolvedColumnDef$id = resolvedColumnDef.id) != null ? _resolvedColumnDef$id + : accessorKey ? + typeof String.prototype.replaceAll === "function" ? + accessorKey.replaceAll(".", "_") + : accessorKey.replace(/\./g, "_") + : void 0) != null + ) ? + _ref + : typeof resolvedColumnDef.header === "string" ? resolvedColumnDef.header + : void 0; + let accessorFn; + if (resolvedColumnDef.accessorFn) { + accessorFn = resolvedColumnDef.accessorFn; + } else if (accessorKey) { + if (accessorKey.includes(".")) { + accessorFn = (originalRow) => { + let result = originalRow; + for (const key of accessorKey.split(".")) { + var _result; + result = (_result = result) == null ? void 0 : _result[key]; + } + return result; + }; + } else { + accessorFn = (originalRow) => originalRow[resolvedColumnDef.accessorKey]; + } + } + if (!id) { + throw new Error(); + } + let column = { + id: `${String(id)}`, + accessorFn, + parent, + depth, + columnDef: resolvedColumnDef, + columns: [], + getFlatColumns: memo( + () => [true], + () => { + var _column$columns; + return [column, ...((_column$columns = column.columns) == null ? void 0 : _column$columns.flatMap((d) => d.getFlatColumns()))]; + }, + getMemoOptions(table.options, "debugColumns"), + ), + getLeafColumns: memo( + () => [table._getOrderColumnsFn()], + (orderColumns2) => { + var _column$columns2; + if ((_column$columns2 = column.columns) != null && _column$columns2.length) { + let leafColumns = column.columns.flatMap((column2) => column2.getLeafColumns()); + return orderColumns2(leafColumns); + } + return [column]; + }, + getMemoOptions(table.options, "debugColumns"), + ), + }; + for (const feature of table._features) { + feature.createColumn == null || feature.createColumn(column, table); + } + return column; +} +const debug = "debugHeaders"; +function createHeader(table, column, options) { + var _options$id; + const id = (_options$id = options.id) != null ? _options$id : column.id; + let header = { + id, + column, + index: options.index, + isPlaceholder: !!options.isPlaceholder, + placeholderId: options.placeholderId, + depth: options.depth, + subHeaders: [], + colSpan: 0, + rowSpan: 0, + headerGroup: null, + getLeafHeaders: () => { + const leafHeaders = []; + const recurseHeader = (h) => { + if (h.subHeaders && h.subHeaders.length) { + h.subHeaders.map(recurseHeader); + } + leafHeaders.push(h); + }; + recurseHeader(header); + return leafHeaders; + }, + getContext: () => ({ + table, + header, + column, + }), + }; + table._features.forEach((feature) => { + feature.createHeader == null || feature.createHeader(header, table); + }); + return header; +} +const Headers$1 = { + createTable: (table) => { + table.getHeaderGroups = memo( + () => [table.getAllColumns(), table.getVisibleLeafColumns(), table.getState().columnPinning.left, table.getState().columnPinning.right], + (allColumns, leafColumns, left, right) => { + var _left$map$filter, _right$map$filter; + const leftColumns = (_left$map$filter = left == null ? void 0 : left.map((columnId) => leafColumns.find((d) => d.id === columnId)).filter(Boolean)) != null ? _left$map$filter : []; + const rightColumns = + (_right$map$filter = right == null ? void 0 : right.map((columnId) => leafColumns.find((d) => d.id === columnId)).filter(Boolean)) != null ? _right$map$filter : []; + const centerColumns = leafColumns.filter((column) => !(left != null && left.includes(column.id)) && !(right != null && right.includes(column.id))); + const headerGroups = buildHeaderGroups(allColumns, [...leftColumns, ...centerColumns, ...rightColumns], table); + return headerGroups; + }, + getMemoOptions(table.options, debug), + ); + table.getCenterHeaderGroups = memo( + () => [table.getAllColumns(), table.getVisibleLeafColumns(), table.getState().columnPinning.left, table.getState().columnPinning.right], + (allColumns, leafColumns, left, right) => { + leafColumns = leafColumns.filter((column) => !(left != null && left.includes(column.id)) && !(right != null && right.includes(column.id))); + return buildHeaderGroups(allColumns, leafColumns, table, "center"); + }, + getMemoOptions(table.options, debug), + ); + table.getLeftHeaderGroups = memo( + () => [table.getAllColumns(), table.getVisibleLeafColumns(), table.getState().columnPinning.left], + (allColumns, leafColumns, left) => { + var _left$map$filter2; + const orderedLeafColumns = + (_left$map$filter2 = left == null ? void 0 : left.map((columnId) => leafColumns.find((d) => d.id === columnId)).filter(Boolean)) != null ? _left$map$filter2 : []; + return buildHeaderGroups(allColumns, orderedLeafColumns, table, "left"); + }, + getMemoOptions(table.options, debug), + ); + table.getRightHeaderGroups = memo( + () => [table.getAllColumns(), table.getVisibleLeafColumns(), table.getState().columnPinning.right], + (allColumns, leafColumns, right) => { + var _right$map$filter2; + const orderedLeafColumns = + (_right$map$filter2 = right == null ? void 0 : right.map((columnId) => leafColumns.find((d) => d.id === columnId)).filter(Boolean)) != null ? _right$map$filter2 : []; + return buildHeaderGroups(allColumns, orderedLeafColumns, table, "right"); + }, + getMemoOptions(table.options, debug), + ); + table.getFooterGroups = memo( + () => [table.getHeaderGroups()], + (headerGroups) => { + return [...headerGroups].reverse(); + }, + getMemoOptions(table.options, debug), + ); + table.getLeftFooterGroups = memo( + () => [table.getLeftHeaderGroups()], + (headerGroups) => { + return [...headerGroups].reverse(); + }, + getMemoOptions(table.options, debug), + ); + table.getCenterFooterGroups = memo( + () => [table.getCenterHeaderGroups()], + (headerGroups) => { + return [...headerGroups].reverse(); + }, + getMemoOptions(table.options, debug), + ); + table.getRightFooterGroups = memo( + () => [table.getRightHeaderGroups()], + (headerGroups) => { + return [...headerGroups].reverse(); + }, + getMemoOptions(table.options, debug), + ); + table.getFlatHeaders = memo( + () => [table.getHeaderGroups()], + (headerGroups) => { + return headerGroups + .map((headerGroup) => { + return headerGroup.headers; + }) + .flat(); + }, + getMemoOptions(table.options, debug), + ); + table.getLeftFlatHeaders = memo( + () => [table.getLeftHeaderGroups()], + (left) => { + return left + .map((headerGroup) => { + return headerGroup.headers; + }) + .flat(); + }, + getMemoOptions(table.options, debug), + ); + table.getCenterFlatHeaders = memo( + () => [table.getCenterHeaderGroups()], + (left) => { + return left + .map((headerGroup) => { + return headerGroup.headers; + }) + .flat(); + }, + getMemoOptions(table.options, debug), + ); + table.getRightFlatHeaders = memo( + () => [table.getRightHeaderGroups()], + (left) => { + return left + .map((headerGroup) => { + return headerGroup.headers; + }) + .flat(); + }, + getMemoOptions(table.options, debug), + ); + table.getCenterLeafHeaders = memo( + () => [table.getCenterFlatHeaders()], + (flatHeaders) => { + return flatHeaders.filter((header) => { + var _header$subHeaders; + return !((_header$subHeaders = header.subHeaders) != null && _header$subHeaders.length); + }); + }, + getMemoOptions(table.options, debug), + ); + table.getLeftLeafHeaders = memo( + () => [table.getLeftFlatHeaders()], + (flatHeaders) => { + return flatHeaders.filter((header) => { + var _header$subHeaders2; + return !((_header$subHeaders2 = header.subHeaders) != null && _header$subHeaders2.length); + }); + }, + getMemoOptions(table.options, debug), + ); + table.getRightLeafHeaders = memo( + () => [table.getRightFlatHeaders()], + (flatHeaders) => { + return flatHeaders.filter((header) => { + var _header$subHeaders3; + return !((_header$subHeaders3 = header.subHeaders) != null && _header$subHeaders3.length); + }); + }, + getMemoOptions(table.options, debug), + ); + table.getLeafHeaders = memo( + () => [table.getLeftHeaderGroups(), table.getCenterHeaderGroups(), table.getRightHeaderGroups()], + (left, center, right) => { + var _left$0$headers, _left$, _center$0$headers, _center$, _right$0$headers, _right$; + return [ + ...((_left$0$headers = (_left$ = left[0]) == null ? void 0 : _left$.headers) != null ? _left$0$headers : []), + ...((_center$0$headers = (_center$ = center[0]) == null ? void 0 : _center$.headers) != null ? _center$0$headers : []), + ...((_right$0$headers = (_right$ = right[0]) == null ? void 0 : _right$.headers) != null ? _right$0$headers : []), + ] + .map((header) => { + return header.getLeafHeaders(); + }) + .flat(); + }, + getMemoOptions(table.options, debug), + ); + }, +}; +function buildHeaderGroups(allColumns, columnsToGroup, table, headerFamily) { + var _headerGroups$0$heade, _headerGroups$; + let maxDepth = 0; + const findMaxDepth = function (columns, depth) { + if (depth === void 0) { + depth = 1; + } + maxDepth = Math.max(maxDepth, depth); + columns + .filter((column) => column.getIsVisible()) + .forEach((column) => { + var _column$columns; + if ((_column$columns = column.columns) != null && _column$columns.length) { + findMaxDepth(column.columns, depth + 1); + } + }, 0); + }; + findMaxDepth(allColumns); + let headerGroups = []; + const createHeaderGroup = (headersToGroup, depth) => { + const headerGroup = { + depth, + id: [headerFamily, `${depth}`].filter(Boolean).join("_"), + headers: [], + }; + const pendingParentHeaders = []; + headersToGroup.forEach((headerToGroup) => { + const latestPendingParentHeader = [...pendingParentHeaders].reverse()[0]; + const isLeafHeader = headerToGroup.column.depth === headerGroup.depth; + let column; + let isPlaceholder = false; + if (isLeafHeader && headerToGroup.column.parent) { + column = headerToGroup.column.parent; + } else { + column = headerToGroup.column; + isPlaceholder = true; + } + if (latestPendingParentHeader && (latestPendingParentHeader == null ? void 0 : latestPendingParentHeader.column) === column) { + latestPendingParentHeader.subHeaders.push(headerToGroup); + } else { + const header = createHeader(table, column, { + id: [headerFamily, depth, column.id, headerToGroup == null ? void 0 : headerToGroup.id].filter(Boolean).join("_"), + isPlaceholder, + placeholderId: isPlaceholder ? `${pendingParentHeaders.filter((d) => d.column === column).length}` : void 0, + depth, + index: pendingParentHeaders.length, + }); + header.subHeaders.push(headerToGroup); + pendingParentHeaders.push(header); + } + headerGroup.headers.push(headerToGroup); + headerToGroup.headerGroup = headerGroup; + }); + headerGroups.push(headerGroup); + if (depth > 0) { + createHeaderGroup(pendingParentHeaders, depth - 1); + } + }; + const bottomHeaders = columnsToGroup.map((column, index) => + createHeader(table, column, { + depth: maxDepth, + index, + }), + ); + createHeaderGroup(bottomHeaders, maxDepth - 1); + headerGroups.reverse(); + const recurseHeadersForSpans = (headers) => { + const filteredHeaders = headers.filter((header) => header.column.getIsVisible()); + return filteredHeaders.map((header) => { + let colSpan = 0; + let rowSpan = 0; + let childRowSpans = [0]; + if (header.subHeaders && header.subHeaders.length) { + childRowSpans = []; + recurseHeadersForSpans(header.subHeaders).forEach((_ref) => { + let { colSpan: childColSpan, rowSpan: childRowSpan } = _ref; + colSpan += childColSpan; + childRowSpans.push(childRowSpan); + }); + } else { + colSpan = 1; + } + const minChildRowSpan = Math.min(...childRowSpans); + rowSpan = rowSpan + minChildRowSpan; + header.colSpan = colSpan; + header.rowSpan = rowSpan; + return { + colSpan, + rowSpan, + }; + }); + }; + recurseHeadersForSpans((_headerGroups$0$heade = (_headerGroups$ = headerGroups[0]) == null ? void 0 : _headerGroups$.headers) != null ? _headerGroups$0$heade : []); + return headerGroups; +} +const createRow = (table, id, original, rowIndex, depth, subRows, parentId) => { + let row = { + id, + index: rowIndex, + original, + depth, + parentId, + _valuesCache: {}, + _uniqueValuesCache: {}, + getValue: (columnId) => { + if (row._valuesCache.hasOwnProperty(columnId)) { + return row._valuesCache[columnId]; + } + const column = table.getColumn(columnId); + if (!(column != null && column.accessorFn)) { + return void 0; + } + row._valuesCache[columnId] = column.accessorFn(row.original, rowIndex); + return row._valuesCache[columnId]; + }, + getUniqueValues: (columnId) => { + if (row._uniqueValuesCache.hasOwnProperty(columnId)) { + return row._uniqueValuesCache[columnId]; + } + const column = table.getColumn(columnId); + if (!(column != null && column.accessorFn)) { + return void 0; + } + if (!column.columnDef.getUniqueValues) { + row._uniqueValuesCache[columnId] = [row.getValue(columnId)]; + return row._uniqueValuesCache[columnId]; + } + row._uniqueValuesCache[columnId] = column.columnDef.getUniqueValues(row.original, rowIndex); + return row._uniqueValuesCache[columnId]; + }, + renderValue: (columnId) => { + var _row$getValue; + return (_row$getValue = row.getValue(columnId)) != null ? _row$getValue : table.options.renderFallbackValue; + }, + subRows: [], + getLeafRows: () => flattenBy(row.subRows, (d) => d.subRows), + getParentRow: () => (row.parentId ? table.getRow(row.parentId, true) : void 0), + getParentRows: () => { + let parentRows = []; + let currentRow = row; + while (true) { + const parentRow = currentRow.getParentRow(); + if (!parentRow) break; + parentRows.push(parentRow); + currentRow = parentRow; + } + return parentRows.reverse(); + }, + getAllCells: memo( + () => [table.getAllLeafColumns()], + (leafColumns) => { + return leafColumns.map((column) => { + return createCell(table, row, column, column.id); + }); + }, + getMemoOptions(table.options, "debugRows"), + ), + _getAllCellsByColumnId: memo( + () => [row.getAllCells()], + (allCells) => { + return allCells.reduce((acc, cell) => { + acc[cell.column.id] = cell; + return acc; + }, {}); + }, + getMemoOptions(table.options, "debugRows"), + ), + }; + for (let i = 0; i < table._features.length; i++) { + const feature = table._features[i]; + feature == null || feature.createRow == null || feature.createRow(row, table); + } + return row; +}; +const ColumnFaceting = { + createColumn: (column, table) => { + column._getFacetedRowModel = table.options.getFacetedRowModel && table.options.getFacetedRowModel(table, column.id); + column.getFacetedRowModel = () => { + if (!column._getFacetedRowModel) { + return table.getPreFilteredRowModel(); + } + return column._getFacetedRowModel(); + }; + column._getFacetedUniqueValues = table.options.getFacetedUniqueValues && table.options.getFacetedUniqueValues(table, column.id); + column.getFacetedUniqueValues = () => { + if (!column._getFacetedUniqueValues) { + return /* @__PURE__ */ new Map(); + } + return column._getFacetedUniqueValues(); + }; + column._getFacetedMinMaxValues = table.options.getFacetedMinMaxValues && table.options.getFacetedMinMaxValues(table, column.id); + column.getFacetedMinMaxValues = () => { + if (!column._getFacetedMinMaxValues) { + return void 0; + } + return column._getFacetedMinMaxValues(); + }; + }, +}; +const includesString = (row, columnId, filterValue) => { + var _filterValue$toString, _row$getValue; + const search = filterValue == null || (_filterValue$toString = filterValue.toString()) == null ? void 0 : _filterValue$toString.toLowerCase(); + return Boolean( + (_row$getValue = row.getValue(columnId)) == null || (_row$getValue = _row$getValue.toString()) == null || (_row$getValue = _row$getValue.toLowerCase()) == null ? + void 0 + : _row$getValue.includes(search), + ); +}; +includesString.autoRemove = (val) => testFalsey(val); +const includesStringSensitive = (row, columnId, filterValue) => { + var _row$getValue2; + return Boolean((_row$getValue2 = row.getValue(columnId)) == null || (_row$getValue2 = _row$getValue2.toString()) == null ? void 0 : _row$getValue2.includes(filterValue)); +}; +includesStringSensitive.autoRemove = (val) => testFalsey(val); +const equalsString = (row, columnId, filterValue) => { + var _row$getValue3; + return ( + ((_row$getValue3 = row.getValue(columnId)) == null || (_row$getValue3 = _row$getValue3.toString()) == null ? void 0 : _row$getValue3.toLowerCase()) === + (filterValue == null ? void 0 : filterValue.toLowerCase()) + ); +}; +equalsString.autoRemove = (val) => testFalsey(val); +const arrIncludes = (row, columnId, filterValue) => { + var _row$getValue4; + return (_row$getValue4 = row.getValue(columnId)) == null ? void 0 : _row$getValue4.includes(filterValue); +}; +arrIncludes.autoRemove = (val) => testFalsey(val); +const arrIncludesAll = (row, columnId, filterValue) => { + return !filterValue.some((val) => { + var _row$getValue5; + return !((_row$getValue5 = row.getValue(columnId)) != null && _row$getValue5.includes(val)); + }); +}; +arrIncludesAll.autoRemove = (val) => testFalsey(val) || !(val != null && val.length); +const arrIncludesSome = (row, columnId, filterValue) => { + return filterValue.some((val) => { + var _row$getValue6; + return (_row$getValue6 = row.getValue(columnId)) == null ? void 0 : _row$getValue6.includes(val); + }); +}; +arrIncludesSome.autoRemove = (val) => testFalsey(val) || !(val != null && val.length); +const equals = (row, columnId, filterValue) => { + return row.getValue(columnId) === filterValue; +}; +equals.autoRemove = (val) => testFalsey(val); +const weakEquals = (row, columnId, filterValue) => { + return row.getValue(columnId) == filterValue; +}; +weakEquals.autoRemove = (val) => testFalsey(val); +const inNumberRange = (row, columnId, filterValue) => { + let [min2, max2] = filterValue; + const rowValue = row.getValue(columnId); + return rowValue >= min2 && rowValue <= max2; +}; +inNumberRange.resolveFilterValue = (val) => { + let [unsafeMin, unsafeMax] = val; + let parsedMin = typeof unsafeMin !== "number" ? parseFloat(unsafeMin) : unsafeMin; + let parsedMax = typeof unsafeMax !== "number" ? parseFloat(unsafeMax) : unsafeMax; + let min2 = unsafeMin === null || Number.isNaN(parsedMin) ? -Infinity : parsedMin; + let max2 = unsafeMax === null || Number.isNaN(parsedMax) ? Infinity : parsedMax; + if (min2 > max2) { + const temp = min2; + min2 = max2; + max2 = temp; + } + return [min2, max2]; +}; +inNumberRange.autoRemove = (val) => testFalsey(val) || (testFalsey(val[0]) && testFalsey(val[1])); +const filterFns = { + includesString, + includesStringSensitive, + equalsString, + arrIncludes, + arrIncludesAll, + arrIncludesSome, + equals, + weakEquals, + inNumberRange, +}; +function testFalsey(val) { + return val === void 0 || val === null || val === ""; +} +const ColumnFiltering = { + getDefaultColumnDef: () => { + return { + filterFn: "auto", + }; + }, + getInitialState: (state) => { + return { + columnFilters: [], + ...state, + }; + }, + getDefaultOptions: (table) => { + return { + onColumnFiltersChange: makeStateUpdater("columnFilters", table), + filterFromLeafRows: false, + maxLeafRowFilterDepth: 100, + }; + }, + createColumn: (column, table) => { + column.getAutoFilterFn = () => { + const firstRow = table.getCoreRowModel().flatRows[0]; + const value = firstRow == null ? void 0 : firstRow.getValue(column.id); + if (typeof value === "string") { + return filterFns.includesString; + } + if (typeof value === "number") { + return filterFns.inNumberRange; + } + if (typeof value === "boolean") { + return filterFns.equals; + } + if (value !== null && typeof value === "object") { + return filterFns.equals; + } + if (Array.isArray(value)) { + return filterFns.arrIncludes; + } + return filterFns.weakEquals; + }; + column.getFilterFn = () => { + var _table$options$filter, _table$options$filter2; + return ( + isFunction(column.columnDef.filterFn) ? column.columnDef.filterFn + : column.columnDef.filterFn === "auto" ? column.getAutoFilterFn() + // @ts-ignore + : (_table$options$filter = (_table$options$filter2 = table.options.filterFns) == null ? void 0 : _table$options$filter2[column.columnDef.filterFn]) != null ? _table$options$filter + : filterFns[column.columnDef.filterFn] + ); + }; + column.getCanFilter = () => { + var _column$columnDef$ena, _table$options$enable, _table$options$enable2; + return ( + ((_column$columnDef$ena = column.columnDef.enableColumnFilter) != null ? _column$columnDef$ena : true) && + ((_table$options$enable = table.options.enableColumnFilters) != null ? _table$options$enable : true) && + ((_table$options$enable2 = table.options.enableFilters) != null ? _table$options$enable2 : true) && + !!column.accessorFn + ); + }; + column.getIsFiltered = () => column.getFilterIndex() > -1; + column.getFilterValue = () => { + var _table$getState$colum; + return (_table$getState$colum = table.getState().columnFilters) == null || (_table$getState$colum = _table$getState$colum.find((d) => d.id === column.id)) == null ? + void 0 + : _table$getState$colum.value; + }; + column.getFilterIndex = () => { + var _table$getState$colum2, _table$getState$colum3; + return (_table$getState$colum2 = (_table$getState$colum3 = table.getState().columnFilters) == null ? void 0 : _table$getState$colum3.findIndex((d) => d.id === column.id)) != null ? + _table$getState$colum2 + : -1; + }; + column.setFilterValue = (value) => { + table.setColumnFilters((old) => { + const filterFn = column.getFilterFn(); + const previousFilter = old == null ? void 0 : old.find((d) => d.id === column.id); + const newFilter = functionalUpdate(value, previousFilter ? previousFilter.value : void 0); + if (shouldAutoRemoveFilter(filterFn, newFilter, column)) { + var _old$filter; + return (_old$filter = old == null ? void 0 : old.filter((d) => d.id !== column.id)) != null ? _old$filter : []; + } + const newFilterObj = { + id: column.id, + value: newFilter, + }; + if (previousFilter) { + var _old$map; + return ( + (_old$map = + old == null ? void 0 : ( + old.map((d) => { + if (d.id === column.id) { + return newFilterObj; + } + return d; + }) + )) != null + ) ? + _old$map + : []; + } + if (old != null && old.length) { + return [...old, newFilterObj]; + } + return [newFilterObj]; + }); + }; + }, + createRow: (row, _table) => { + row.columnFilters = {}; + row.columnFiltersMeta = {}; + }, + createTable: (table) => { + table.setColumnFilters = (updater) => { + const leafColumns = table.getAllLeafColumns(); + const updateFn = (old) => { + var _functionalUpdate; + return (_functionalUpdate = functionalUpdate(updater, old)) == null ? + void 0 + : _functionalUpdate.filter((filter) => { + const column = leafColumns.find((d) => d.id === filter.id); + if (column) { + const filterFn = column.getFilterFn(); + if (shouldAutoRemoveFilter(filterFn, filter.value, column)) { + return false; + } + } + return true; + }); + }; + table.options.onColumnFiltersChange == null || table.options.onColumnFiltersChange(updateFn); + }; + table.resetColumnFilters = (defaultState) => { + var _table$initialState$c, _table$initialState; + table.setColumnFilters( + defaultState ? [] + : (_table$initialState$c = (_table$initialState = table.initialState) == null ? void 0 : _table$initialState.columnFilters) != null ? _table$initialState$c + : [], + ); + }; + table.getPreFilteredRowModel = () => table.getCoreRowModel(); + table.getFilteredRowModel = () => { + if (!table._getFilteredRowModel && table.options.getFilteredRowModel) { + table._getFilteredRowModel = table.options.getFilteredRowModel(table); + } + if (table.options.manualFiltering || !table._getFilteredRowModel) { + return table.getPreFilteredRowModel(); + } + return table._getFilteredRowModel(); + }; + }, +}; +function shouldAutoRemoveFilter(filterFn, value, column) { + return (filterFn && filterFn.autoRemove ? filterFn.autoRemove(value, column) : false) || typeof value === "undefined" || (typeof value === "string" && !value); +} +const sum = (columnId, _leafRows, childRows) => { + return childRows.reduce((sum2, next) => { + const nextValue = next.getValue(columnId); + return sum2 + (typeof nextValue === "number" ? nextValue : 0); + }, 0); +}; +const min = (columnId, _leafRows, childRows) => { + let min2; + childRows.forEach((row) => { + const value = row.getValue(columnId); + if (value != null && (min2 > value || (min2 === void 0 && value >= value))) { + min2 = value; + } + }); + return min2; +}; +const max = (columnId, _leafRows, childRows) => { + let max2; + childRows.forEach((row) => { + const value = row.getValue(columnId); + if (value != null && (max2 < value || (max2 === void 0 && value >= value))) { + max2 = value; + } + }); + return max2; +}; +const extent = (columnId, _leafRows, childRows) => { + let min2; + let max2; + childRows.forEach((row) => { + const value = row.getValue(columnId); + if (value != null) { + if (min2 === void 0) { + if (value >= value) min2 = max2 = value; + } else { + if (min2 > value) min2 = value; + if (max2 < value) max2 = value; + } + } + }); + return [min2, max2]; +}; +const mean = (columnId, leafRows) => { + let count2 = 0; + let sum2 = 0; + leafRows.forEach((row) => { + let value = row.getValue(columnId); + if (value != null && (value = +value) >= value) { + (++count2, (sum2 += value)); + } + }); + if (count2) return sum2 / count2; + return; +}; +const median = (columnId, leafRows) => { + if (!leafRows.length) { + return; + } + const values = leafRows.map((row) => row.getValue(columnId)); + if (!isNumberArray(values)) { + return; + } + if (values.length === 1) { + return values[0]; + } + const mid = Math.floor(values.length / 2); + const nums = values.sort((a, b) => a - b); + return values.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2; +}; +const unique = (columnId, leafRows) => { + return Array.from(new Set(leafRows.map((d) => d.getValue(columnId))).values()); +}; +const uniqueCount = (columnId, leafRows) => { + return new Set(leafRows.map((d) => d.getValue(columnId))).size; +}; +const count = (_columnId, leafRows) => { + return leafRows.length; +}; +const aggregationFns = { + sum, + min, + max, + extent, + mean, + median, + unique, + uniqueCount, + count, +}; +const ColumnGrouping = { + getDefaultColumnDef: () => { + return { + aggregatedCell: (props) => { + var _toString, _props$getValue; + return (_toString = (_props$getValue = props.getValue()) == null || _props$getValue.toString == null ? void 0 : _props$getValue.toString()) != null ? _toString : null; + }, + aggregationFn: "auto", + }; + }, + getInitialState: (state) => { + return { + grouping: [], + ...state, + }; + }, + getDefaultOptions: (table) => { + return { + onGroupingChange: makeStateUpdater("grouping", table), + groupedColumnMode: "reorder", + }; + }, + createColumn: (column, table) => { + column.toggleGrouping = () => { + table.setGrouping((old) => { + if (old != null && old.includes(column.id)) { + return old.filter((d) => d !== column.id); + } + return [...(old != null ? old : []), column.id]; + }); + }; + column.getCanGroup = () => { + var _column$columnDef$ena, _table$options$enable; + return ( + ((_column$columnDef$ena = column.columnDef.enableGrouping) != null ? _column$columnDef$ena : true) && + ((_table$options$enable = table.options.enableGrouping) != null ? _table$options$enable : true) && + (!!column.accessorFn || !!column.columnDef.getGroupingValue) + ); + }; + column.getIsGrouped = () => { + var _table$getState$group; + return (_table$getState$group = table.getState().grouping) == null ? void 0 : _table$getState$group.includes(column.id); + }; + column.getGroupedIndex = () => { + var _table$getState$group2; + return (_table$getState$group2 = table.getState().grouping) == null ? void 0 : _table$getState$group2.indexOf(column.id); + }; + column.getToggleGroupingHandler = () => { + const canGroup = column.getCanGroup(); + return () => { + if (!canGroup) return; + column.toggleGrouping(); + }; + }; + column.getAutoAggregationFn = () => { + const firstRow = table.getCoreRowModel().flatRows[0]; + const value = firstRow == null ? void 0 : firstRow.getValue(column.id); + if (typeof value === "number") { + return aggregationFns.sum; + } + if (Object.prototype.toString.call(value) === "[object Date]") { + return aggregationFns.extent; + } + }; + column.getAggregationFn = () => { + var _table$options$aggreg, _table$options$aggreg2; + if (!column) { + throw new Error(); + } + return ( + isFunction(column.columnDef.aggregationFn) ? column.columnDef.aggregationFn + : column.columnDef.aggregationFn === "auto" ? column.getAutoAggregationFn() + : (_table$options$aggreg = (_table$options$aggreg2 = table.options.aggregationFns) == null ? void 0 : _table$options$aggreg2[column.columnDef.aggregationFn]) != null ? + _table$options$aggreg + : aggregationFns[column.columnDef.aggregationFn] + ); + }; + }, + createTable: (table) => { + table.setGrouping = (updater) => (table.options.onGroupingChange == null ? void 0 : table.options.onGroupingChange(updater)); + table.resetGrouping = (defaultState) => { + var _table$initialState$g, _table$initialState; + table.setGrouping( + defaultState ? [] + : (_table$initialState$g = (_table$initialState = table.initialState) == null ? void 0 : _table$initialState.grouping) != null ? _table$initialState$g + : [], + ); + }; + table.getPreGroupedRowModel = () => table.getFilteredRowModel(); + table.getGroupedRowModel = () => { + if (!table._getGroupedRowModel && table.options.getGroupedRowModel) { + table._getGroupedRowModel = table.options.getGroupedRowModel(table); + } + if (table.options.manualGrouping || !table._getGroupedRowModel) { + return table.getPreGroupedRowModel(); + } + return table._getGroupedRowModel(); + }; + }, + createRow: (row, table) => { + row.getIsGrouped = () => !!row.groupingColumnId; + row.getGroupingValue = (columnId) => { + if (row._groupingValuesCache.hasOwnProperty(columnId)) { + return row._groupingValuesCache[columnId]; + } + const column = table.getColumn(columnId); + if (!(column != null && column.columnDef.getGroupingValue)) { + return row.getValue(columnId); + } + row._groupingValuesCache[columnId] = column.columnDef.getGroupingValue(row.original); + return row._groupingValuesCache[columnId]; + }; + row._groupingValuesCache = {}; + }, + createCell: (cell, column, row, table) => { + cell.getIsGrouped = () => column.getIsGrouped() && column.id === row.groupingColumnId; + cell.getIsPlaceholder = () => !cell.getIsGrouped() && column.getIsGrouped(); + cell.getIsAggregated = () => { + var _row$subRows; + return !cell.getIsGrouped() && !cell.getIsPlaceholder() && !!((_row$subRows = row.subRows) != null && _row$subRows.length); + }; + }, +}; +function orderColumns(leafColumns, grouping, groupedColumnMode) { + if (!(grouping != null && grouping.length) || !groupedColumnMode) { + return leafColumns; + } + const nonGroupingColumns = leafColumns.filter((col) => !grouping.includes(col.id)); + if (groupedColumnMode === "remove") { + return nonGroupingColumns; + } + const groupingColumns = grouping.map((g) => leafColumns.find((col) => col.id === g)).filter(Boolean); + return [...groupingColumns, ...nonGroupingColumns]; +} +const ColumnOrdering = { + getInitialState: (state) => { + return { + columnOrder: [], + ...state, + }; + }, + getDefaultOptions: (table) => { + return { + onColumnOrderChange: makeStateUpdater("columnOrder", table), + }; + }, + createColumn: (column, table) => { + column.getIndex = memo( + (position) => [_getVisibleLeafColumns(table, position)], + (columns) => columns.findIndex((d) => d.id === column.id), + getMemoOptions(table.options, "debugColumns"), + ); + column.getIsFirstColumn = (position) => { + var _columns$; + const columns = _getVisibleLeafColumns(table, position); + return ((_columns$ = columns[0]) == null ? void 0 : _columns$.id) === column.id; + }; + column.getIsLastColumn = (position) => { + var _columns; + const columns = _getVisibleLeafColumns(table, position); + return ((_columns = columns[columns.length - 1]) == null ? void 0 : _columns.id) === column.id; + }; + }, + createTable: (table) => { + table.setColumnOrder = (updater) => (table.options.onColumnOrderChange == null ? void 0 : table.options.onColumnOrderChange(updater)); + table.resetColumnOrder = (defaultState) => { + var _table$initialState$c; + table.setColumnOrder( + defaultState ? [] + : (_table$initialState$c = table.initialState.columnOrder) != null ? _table$initialState$c + : [], + ); + }; + table._getOrderColumnsFn = memo( + () => [table.getState().columnOrder, table.getState().grouping, table.options.groupedColumnMode], + (columnOrder, grouping, groupedColumnMode) => (columns) => { + let orderedColumns = []; + if (!(columnOrder != null && columnOrder.length)) { + orderedColumns = columns; + } else { + const columnOrderCopy = [...columnOrder]; + const columnsCopy = [...columns]; + while (columnsCopy.length && columnOrderCopy.length) { + const targetColumnId = columnOrderCopy.shift(); + const foundIndex = columnsCopy.findIndex((d) => d.id === targetColumnId); + if (foundIndex > -1) { + orderedColumns.push(columnsCopy.splice(foundIndex, 1)[0]); + } + } + orderedColumns = [...orderedColumns, ...columnsCopy]; + } + return orderColumns(orderedColumns, grouping, groupedColumnMode); + }, + getMemoOptions(table.options, "debugTable"), + ); + }, +}; +const getDefaultColumnPinningState = () => ({ + left: [], + right: [], +}); +const ColumnPinning = { + getInitialState: (state) => { + return { + columnPinning: getDefaultColumnPinningState(), + ...state, + }; + }, + getDefaultOptions: (table) => { + return { + onColumnPinningChange: makeStateUpdater("columnPinning", table), + }; + }, + createColumn: (column, table) => { + column.pin = (position) => { + const columnIds = column + .getLeafColumns() + .map((d) => d.id) + .filter(Boolean); + table.setColumnPinning((old) => { + var _old$left3, _old$right3; + if (position === "right") { + var _old$left, _old$right; + return { + left: ((_old$left = old == null ? void 0 : old.left) != null ? _old$left : []).filter((d) => !(columnIds != null && columnIds.includes(d))), + right: [...((_old$right = old == null ? void 0 : old.right) != null ? _old$right : []).filter((d) => !(columnIds != null && columnIds.includes(d))), ...columnIds], + }; + } + if (position === "left") { + var _old$left2, _old$right2; + return { + left: [...((_old$left2 = old == null ? void 0 : old.left) != null ? _old$left2 : []).filter((d) => !(columnIds != null && columnIds.includes(d))), ...columnIds], + right: ((_old$right2 = old == null ? void 0 : old.right) != null ? _old$right2 : []).filter((d) => !(columnIds != null && columnIds.includes(d))), + }; + } + return { + left: ((_old$left3 = old == null ? void 0 : old.left) != null ? _old$left3 : []).filter((d) => !(columnIds != null && columnIds.includes(d))), + right: ((_old$right3 = old == null ? void 0 : old.right) != null ? _old$right3 : []).filter((d) => !(columnIds != null && columnIds.includes(d))), + }; + }); + }; + column.getCanPin = () => { + const leafColumns = column.getLeafColumns(); + return leafColumns.some((d) => { + var _d$columnDef$enablePi, _ref, _table$options$enable; + return ( + ((_d$columnDef$enablePi = d.columnDef.enablePinning) != null ? _d$columnDef$enablePi : true) && + ((_ref = (_table$options$enable = table.options.enableColumnPinning) != null ? _table$options$enable : table.options.enablePinning) != null ? _ref : true) + ); + }); + }; + column.getIsPinned = () => { + const leafColumnIds = column.getLeafColumns().map((d) => d.id); + const { left, right } = table.getState().columnPinning; + const isLeft = leafColumnIds.some((d) => (left == null ? void 0 : left.includes(d))); + const isRight = leafColumnIds.some((d) => (right == null ? void 0 : right.includes(d))); + return ( + isLeft ? "left" + : isRight ? "right" + : false + ); + }; + column.getPinnedIndex = () => { + var _table$getState$colum, _table$getState$colum2; + const position = column.getIsPinned(); + return ( + position ? + ( + (_table$getState$colum = + (_table$getState$colum2 = table.getState().columnPinning) == null || (_table$getState$colum2 = _table$getState$colum2[position]) == null ? + void 0 + : _table$getState$colum2.indexOf(column.id)) != null + ) ? + _table$getState$colum + : -1 + : 0 + ); + }; + }, + createRow: (row, table) => { + row.getCenterVisibleCells = memo( + () => [row._getAllVisibleCells(), table.getState().columnPinning.left, table.getState().columnPinning.right], + (allCells, left, right) => { + const leftAndRight = [...(left != null ? left : []), ...(right != null ? right : [])]; + return allCells.filter((d) => !leftAndRight.includes(d.column.id)); + }, + getMemoOptions(table.options, "debugRows"), + ); + row.getLeftVisibleCells = memo( + () => [row._getAllVisibleCells(), table.getState().columnPinning.left], + (allCells, left) => { + const cells = (left != null ? left : []) + .map((columnId) => allCells.find((cell) => cell.column.id === columnId)) + .filter(Boolean) + .map((d) => ({ + ...d, + position: "left", + })); + return cells; + }, + getMemoOptions(table.options, "debugRows"), + ); + row.getRightVisibleCells = memo( + () => [row._getAllVisibleCells(), table.getState().columnPinning.right], + (allCells, right) => { + const cells = (right != null ? right : []) + .map((columnId) => allCells.find((cell) => cell.column.id === columnId)) + .filter(Boolean) + .map((d) => ({ + ...d, + position: "right", + })); + return cells; + }, + getMemoOptions(table.options, "debugRows"), + ); + }, + createTable: (table) => { + table.setColumnPinning = (updater) => (table.options.onColumnPinningChange == null ? void 0 : table.options.onColumnPinningChange(updater)); + table.resetColumnPinning = (defaultState) => { + var _table$initialState$c, _table$initialState; + return table.setColumnPinning( + defaultState ? getDefaultColumnPinningState() + : (_table$initialState$c = (_table$initialState = table.initialState) == null ? void 0 : _table$initialState.columnPinning) != null ? _table$initialState$c + : getDefaultColumnPinningState(), + ); + }; + table.getIsSomeColumnsPinned = (position) => { + var _pinningState$positio; + const pinningState = table.getState().columnPinning; + if (!position) { + var _pinningState$left, _pinningState$right; + return Boolean( + ((_pinningState$left = pinningState.left) == null ? void 0 : _pinningState$left.length) || + ((_pinningState$right = pinningState.right) == null ? void 0 : _pinningState$right.length), + ); + } + return Boolean((_pinningState$positio = pinningState[position]) == null ? void 0 : _pinningState$positio.length); + }; + table.getLeftLeafColumns = memo( + () => [table.getAllLeafColumns(), table.getState().columnPinning.left], + (allColumns, left) => { + return (left != null ? left : []).map((columnId) => allColumns.find((column) => column.id === columnId)).filter(Boolean); + }, + getMemoOptions(table.options, "debugColumns"), + ); + table.getRightLeafColumns = memo( + () => [table.getAllLeafColumns(), table.getState().columnPinning.right], + (allColumns, right) => { + return (right != null ? right : []).map((columnId) => allColumns.find((column) => column.id === columnId)).filter(Boolean); + }, + getMemoOptions(table.options, "debugColumns"), + ); + table.getCenterLeafColumns = memo( + () => [table.getAllLeafColumns(), table.getState().columnPinning.left, table.getState().columnPinning.right], + (allColumns, left, right) => { + const leftAndRight = [...(left != null ? left : []), ...(right != null ? right : [])]; + return allColumns.filter((d) => !leftAndRight.includes(d.id)); + }, + getMemoOptions(table.options, "debugColumns"), + ); + }, +}; +function safelyAccessDocument(_document) { + return _document || (typeof document !== "undefined" ? document : null); +} +const defaultColumnSizing = { + size: 150, + minSize: 20, + maxSize: Number.MAX_SAFE_INTEGER, +}; +const getDefaultColumnSizingInfoState = () => ({ + startOffset: null, + startSize: null, + deltaOffset: null, + deltaPercentage: null, + isResizingColumn: false, + columnSizingStart: [], +}); +const ColumnSizing = { + getDefaultColumnDef: () => { + return defaultColumnSizing; + }, + getInitialState: (state) => { + return { + columnSizing: {}, + columnSizingInfo: getDefaultColumnSizingInfoState(), + ...state, + }; + }, + getDefaultOptions: (table) => { + return { + columnResizeMode: "onEnd", + columnResizeDirection: "ltr", + onColumnSizingChange: makeStateUpdater("columnSizing", table), + onColumnSizingInfoChange: makeStateUpdater("columnSizingInfo", table), + }; + }, + createColumn: (column, table) => { + column.getSize = () => { + var _column$columnDef$min, _ref, _column$columnDef$max; + const columnSize = table.getState().columnSizing[column.id]; + return Math.min( + Math.max( + (_column$columnDef$min = column.columnDef.minSize) != null ? _column$columnDef$min : defaultColumnSizing.minSize, + (_ref = columnSize != null ? columnSize : column.columnDef.size) != null ? _ref : defaultColumnSizing.size, + ), + (_column$columnDef$max = column.columnDef.maxSize) != null ? _column$columnDef$max : defaultColumnSizing.maxSize, + ); + }; + column.getStart = memo( + (position) => [position, _getVisibleLeafColumns(table, position), table.getState().columnSizing], + (position, columns) => columns.slice(0, column.getIndex(position)).reduce((sum2, column2) => sum2 + column2.getSize(), 0), + getMemoOptions(table.options, "debugColumns"), + ); + column.getAfter = memo( + (position) => [position, _getVisibleLeafColumns(table, position), table.getState().columnSizing], + (position, columns) => columns.slice(column.getIndex(position) + 1).reduce((sum2, column2) => sum2 + column2.getSize(), 0), + getMemoOptions(table.options, "debugColumns"), + ); + column.resetSize = () => { + table.setColumnSizing((_ref2) => { + let { [column.id]: _, ...rest } = _ref2; + return rest; + }); + }; + column.getCanResize = () => { + var _column$columnDef$ena, _table$options$enable; + return ( + ((_column$columnDef$ena = column.columnDef.enableResizing) != null ? _column$columnDef$ena : true) && + ((_table$options$enable = table.options.enableColumnResizing) != null ? _table$options$enable : true) + ); + }; + column.getIsResizing = () => { + return table.getState().columnSizingInfo.isResizingColumn === column.id; + }; + }, + createHeader: (header, table) => { + header.getSize = () => { + let sum2 = 0; + const recurse = (header2) => { + if (header2.subHeaders.length) { + header2.subHeaders.forEach(recurse); + } else { + var _header$column$getSiz; + sum2 += (_header$column$getSiz = header2.column.getSize()) != null ? _header$column$getSiz : 0; + } + }; + recurse(header); + return sum2; + }; + header.getStart = () => { + if (header.index > 0) { + const prevSiblingHeader = header.headerGroup.headers[header.index - 1]; + return prevSiblingHeader.getStart() + prevSiblingHeader.getSize(); + } + return 0; + }; + header.getResizeHandler = (_contextDocument) => { + const column = table.getColumn(header.column.id); + const canResize = column == null ? void 0 : column.getCanResize(); + return (e) => { + if (!column || !canResize) { + return; + } + e.persist == null || e.persist(); + if (isTouchStartEvent(e)) { + if (e.touches && e.touches.length > 1) { + return; + } + } + const startSize = header.getSize(); + const columnSizingStart = header ? header.getLeafHeaders().map((d) => [d.column.id, d.column.getSize()]) : [[column.id, column.getSize()]]; + const clientX = isTouchStartEvent(e) ? Math.round(e.touches[0].clientX) : e.clientX; + const newColumnSizing = {}; + const updateOffset = (eventType, clientXPos) => { + if (typeof clientXPos !== "number") { + return; + } + table.setColumnSizingInfo((old) => { + var _old$startOffset, _old$startSize; + const deltaDirection = table.options.columnResizeDirection === "rtl" ? -1 : 1; + const deltaOffset = (clientXPos - ((_old$startOffset = old == null ? void 0 : old.startOffset) != null ? _old$startOffset : 0)) * deltaDirection; + const deltaPercentage = Math.max(deltaOffset / ((_old$startSize = old == null ? void 0 : old.startSize) != null ? _old$startSize : 0), -0.999999); + old.columnSizingStart.forEach((_ref3) => { + let [columnId, headerSize] = _ref3; + newColumnSizing[columnId] = Math.round(Math.max(headerSize + headerSize * deltaPercentage, 0) * 100) / 100; + }); + return { + ...old, + deltaOffset, + deltaPercentage, + }; + }); + if (table.options.columnResizeMode === "onChange" || eventType === "end") { + table.setColumnSizing((old) => ({ + ...old, + ...newColumnSizing, + })); + } + }; + const onMove = (clientXPos) => updateOffset("move", clientXPos); + const onEnd = (clientXPos) => { + updateOffset("end", clientXPos); + table.setColumnSizingInfo((old) => ({ + ...old, + isResizingColumn: false, + startOffset: null, + startSize: null, + deltaOffset: null, + deltaPercentage: null, + columnSizingStart: [], + })); + }; + const contextDocument = safelyAccessDocument(_contextDocument); + const mouseEvents = { + moveHandler: (e2) => onMove(e2.clientX), + upHandler: (e2) => { + contextDocument == null || contextDocument.removeEventListener("mousemove", mouseEvents.moveHandler); + contextDocument == null || contextDocument.removeEventListener("mouseup", mouseEvents.upHandler); + onEnd(e2.clientX); + }, + }; + const touchEvents = { + moveHandler: (e2) => { + if (e2.cancelable) { + e2.preventDefault(); + e2.stopPropagation(); + } + onMove(e2.touches[0].clientX); + return false; + }, + upHandler: (e2) => { + var _e$touches$; + contextDocument == null || contextDocument.removeEventListener("touchmove", touchEvents.moveHandler); + contextDocument == null || contextDocument.removeEventListener("touchend", touchEvents.upHandler); + if (e2.cancelable) { + e2.preventDefault(); + e2.stopPropagation(); + } + onEnd((_e$touches$ = e2.touches[0]) == null ? void 0 : _e$touches$.clientX); + }, + }; + const passiveIfSupported = + passiveEventSupported() ? + { + passive: false, + } + : false; + if (isTouchStartEvent(e)) { + contextDocument == null || contextDocument.addEventListener("touchmove", touchEvents.moveHandler, passiveIfSupported); + contextDocument == null || contextDocument.addEventListener("touchend", touchEvents.upHandler, passiveIfSupported); + } else { + contextDocument == null || contextDocument.addEventListener("mousemove", mouseEvents.moveHandler, passiveIfSupported); + contextDocument == null || contextDocument.addEventListener("mouseup", mouseEvents.upHandler, passiveIfSupported); + } + table.setColumnSizingInfo((old) => ({ + ...old, + startOffset: clientX, + startSize, + deltaOffset: 0, + deltaPercentage: 0, + columnSizingStart, + isResizingColumn: column.id, + })); + }; + }; + }, + createTable: (table) => { + table.setColumnSizing = (updater) => (table.options.onColumnSizingChange == null ? void 0 : table.options.onColumnSizingChange(updater)); + table.setColumnSizingInfo = (updater) => (table.options.onColumnSizingInfoChange == null ? void 0 : table.options.onColumnSizingInfoChange(updater)); + table.resetColumnSizing = (defaultState) => { + var _table$initialState$c; + table.setColumnSizing( + defaultState ? {} + : (_table$initialState$c = table.initialState.columnSizing) != null ? _table$initialState$c + : {}, + ); + }; + table.resetHeaderSizeInfo = (defaultState) => { + var _table$initialState$c2; + table.setColumnSizingInfo( + defaultState ? getDefaultColumnSizingInfoState() + : (_table$initialState$c2 = table.initialState.columnSizingInfo) != null ? _table$initialState$c2 + : getDefaultColumnSizingInfoState(), + ); + }; + table.getTotalSize = () => { + var _table$getHeaderGroup, _table$getHeaderGroup2; + return ( + (_table$getHeaderGroup = + (_table$getHeaderGroup2 = table.getHeaderGroups()[0]) == null ? + void 0 + : _table$getHeaderGroup2.headers.reduce((sum2, header) => { + return sum2 + header.getSize(); + }, 0)) != null + ) ? + _table$getHeaderGroup + : 0; + }; + table.getLeftTotalSize = () => { + var _table$getLeftHeaderG, _table$getLeftHeaderG2; + return ( + (_table$getLeftHeaderG = + (_table$getLeftHeaderG2 = table.getLeftHeaderGroups()[0]) == null ? + void 0 + : _table$getLeftHeaderG2.headers.reduce((sum2, header) => { + return sum2 + header.getSize(); + }, 0)) != null + ) ? + _table$getLeftHeaderG + : 0; + }; + table.getCenterTotalSize = () => { + var _table$getCenterHeade, _table$getCenterHeade2; + return ( + (_table$getCenterHeade = + (_table$getCenterHeade2 = table.getCenterHeaderGroups()[0]) == null ? + void 0 + : _table$getCenterHeade2.headers.reduce((sum2, header) => { + return sum2 + header.getSize(); + }, 0)) != null + ) ? + _table$getCenterHeade + : 0; + }; + table.getRightTotalSize = () => { + var _table$getRightHeader, _table$getRightHeader2; + return ( + (_table$getRightHeader = + (_table$getRightHeader2 = table.getRightHeaderGroups()[0]) == null ? + void 0 + : _table$getRightHeader2.headers.reduce((sum2, header) => { + return sum2 + header.getSize(); + }, 0)) != null + ) ? + _table$getRightHeader + : 0; + }; + }, +}; +let passiveSupported = null; +function passiveEventSupported() { + if (typeof passiveSupported === "boolean") return passiveSupported; + let supported = false; + try { + const options = { + get passive() { + supported = true; + return false; + }, + }; + const noop2 = () => {}; + window.addEventListener("test", noop2, options); + window.removeEventListener("test", noop2); + } catch (err) { + supported = false; + } + passiveSupported = supported; + return passiveSupported; +} +function isTouchStartEvent(e) { + return e.type === "touchstart"; +} +const ColumnVisibility = { + getInitialState: (state) => { + return { + columnVisibility: {}, + ...state, + }; + }, + getDefaultOptions: (table) => { + return { + onColumnVisibilityChange: makeStateUpdater("columnVisibility", table), + }; + }, + createColumn: (column, table) => { + column.toggleVisibility = (value) => { + if (column.getCanHide()) { + table.setColumnVisibility((old) => ({ + ...old, + [column.id]: value != null ? value : !column.getIsVisible(), + })); + } + }; + column.getIsVisible = () => { + var _ref, _table$getState$colum; + const childColumns = column.columns; + return ( + (_ref = + childColumns.length ? childColumns.some((c) => c.getIsVisible()) + : (_table$getState$colum = table.getState().columnVisibility) == null ? void 0 + : _table$getState$colum[column.id]) != null + ) ? + _ref + : true; + }; + column.getCanHide = () => { + var _column$columnDef$ena, _table$options$enable; + return ( + ((_column$columnDef$ena = column.columnDef.enableHiding) != null ? _column$columnDef$ena : true) && + ((_table$options$enable = table.options.enableHiding) != null ? _table$options$enable : true) + ); + }; + column.getToggleVisibilityHandler = () => { + return (e) => { + column.toggleVisibility == null || column.toggleVisibility(e.target.checked); + }; + }; + }, + createRow: (row, table) => { + row._getAllVisibleCells = memo( + () => [row.getAllCells(), table.getState().columnVisibility], + (cells) => { + return cells.filter((cell) => cell.column.getIsVisible()); + }, + getMemoOptions(table.options, "debugRows"), + ); + row.getVisibleCells = memo( + () => [row.getLeftVisibleCells(), row.getCenterVisibleCells(), row.getRightVisibleCells()], + (left, center, right) => [...left, ...center, ...right], + getMemoOptions(table.options, "debugRows"), + ); + }, + createTable: (table) => { + const makeVisibleColumnsMethod = (key, getColumns) => { + return memo( + () => [ + getColumns(), + getColumns() + .filter((d) => d.getIsVisible()) + .map((d) => d.id) + .join("_"), + ], + (columns) => { + return columns.filter((d) => (d.getIsVisible == null ? void 0 : d.getIsVisible())); + }, + getMemoOptions(table.options, "debugColumns"), + ); + }; + table.getVisibleFlatColumns = makeVisibleColumnsMethod("getVisibleFlatColumns", () => table.getAllFlatColumns()); + table.getVisibleLeafColumns = makeVisibleColumnsMethod("getVisibleLeafColumns", () => table.getAllLeafColumns()); + table.getLeftVisibleLeafColumns = makeVisibleColumnsMethod("getLeftVisibleLeafColumns", () => table.getLeftLeafColumns()); + table.getRightVisibleLeafColumns = makeVisibleColumnsMethod("getRightVisibleLeafColumns", () => table.getRightLeafColumns()); + table.getCenterVisibleLeafColumns = makeVisibleColumnsMethod("getCenterVisibleLeafColumns", () => table.getCenterLeafColumns()); + table.setColumnVisibility = (updater) => (table.options.onColumnVisibilityChange == null ? void 0 : table.options.onColumnVisibilityChange(updater)); + table.resetColumnVisibility = (defaultState) => { + var _table$initialState$c; + table.setColumnVisibility( + defaultState ? {} + : (_table$initialState$c = table.initialState.columnVisibility) != null ? _table$initialState$c + : {}, + ); + }; + table.toggleAllColumnsVisible = (value) => { + var _value; + value = (_value = value) != null ? _value : !table.getIsAllColumnsVisible(); + table.setColumnVisibility( + table.getAllLeafColumns().reduce( + (obj, column) => ({ + ...obj, + [column.id]: !value ? !(column.getCanHide != null && column.getCanHide()) : value, + }), + {}, + ), + ); + }; + table.getIsAllColumnsVisible = () => !table.getAllLeafColumns().some((column) => !(column.getIsVisible != null && column.getIsVisible())); + table.getIsSomeColumnsVisible = () => table.getAllLeafColumns().some((column) => (column.getIsVisible == null ? void 0 : column.getIsVisible())); + table.getToggleAllColumnsVisibilityHandler = () => { + return (e) => { + var _target; + table.toggleAllColumnsVisible((_target = e.target) == null ? void 0 : _target.checked); + }; + }; + }, +}; +function _getVisibleLeafColumns(table, position) { + return ( + !position ? table.getVisibleLeafColumns() + : position === "center" ? table.getCenterVisibleLeafColumns() + : position === "left" ? table.getLeftVisibleLeafColumns() + : table.getRightVisibleLeafColumns() + ); +} +const GlobalFaceting = { + createTable: (table) => { + table._getGlobalFacetedRowModel = table.options.getFacetedRowModel && table.options.getFacetedRowModel(table, "__global__"); + table.getGlobalFacetedRowModel = () => { + if (table.options.manualFiltering || !table._getGlobalFacetedRowModel) { + return table.getPreFilteredRowModel(); + } + return table._getGlobalFacetedRowModel(); + }; + table._getGlobalFacetedUniqueValues = table.options.getFacetedUniqueValues && table.options.getFacetedUniqueValues(table, "__global__"); + table.getGlobalFacetedUniqueValues = () => { + if (!table._getGlobalFacetedUniqueValues) { + return /* @__PURE__ */ new Map(); + } + return table._getGlobalFacetedUniqueValues(); + }; + table._getGlobalFacetedMinMaxValues = table.options.getFacetedMinMaxValues && table.options.getFacetedMinMaxValues(table, "__global__"); + table.getGlobalFacetedMinMaxValues = () => { + if (!table._getGlobalFacetedMinMaxValues) { + return; + } + return table._getGlobalFacetedMinMaxValues(); + }; + }, +}; +const GlobalFiltering = { + getInitialState: (state) => { + return { + globalFilter: void 0, + ...state, + }; + }, + getDefaultOptions: (table) => { + return { + onGlobalFilterChange: makeStateUpdater("globalFilter", table), + globalFilterFn: "auto", + getColumnCanGlobalFilter: (column) => { + var _table$getCoreRowMode; + const value = + (_table$getCoreRowMode = table.getCoreRowModel().flatRows[0]) == null || (_table$getCoreRowMode = _table$getCoreRowMode._getAllCellsByColumnId()[column.id]) == null ? + void 0 + : _table$getCoreRowMode.getValue(); + return typeof value === "string" || typeof value === "number"; + }, + }; + }, + createColumn: (column, table) => { + column.getCanGlobalFilter = () => { + var _column$columnDef$ena, _table$options$enable, _table$options$enable2, _table$options$getCol; + return ( + ((_column$columnDef$ena = column.columnDef.enableGlobalFilter) != null ? _column$columnDef$ena : true) && + ((_table$options$enable = table.options.enableGlobalFilter) != null ? _table$options$enable : true) && + ((_table$options$enable2 = table.options.enableFilters) != null ? _table$options$enable2 : true) && + ((_table$options$getCol = table.options.getColumnCanGlobalFilter == null ? void 0 : table.options.getColumnCanGlobalFilter(column)) != null ? _table$options$getCol : true) && + !!column.accessorFn + ); + }; + }, + createTable: (table) => { + table.getGlobalAutoFilterFn = () => { + return filterFns.includesString; + }; + table.getGlobalFilterFn = () => { + var _table$options$filter, _table$options$filter2; + const { globalFilterFn } = table.options; + return ( + isFunction(globalFilterFn) ? globalFilterFn + : globalFilterFn === "auto" ? table.getGlobalAutoFilterFn() + : (_table$options$filter = (_table$options$filter2 = table.options.filterFns) == null ? void 0 : _table$options$filter2[globalFilterFn]) != null ? _table$options$filter + : filterFns[globalFilterFn] + ); + }; + table.setGlobalFilter = (updater) => { + table.options.onGlobalFilterChange == null || table.options.onGlobalFilterChange(updater); + }; + table.resetGlobalFilter = (defaultState) => { + table.setGlobalFilter(defaultState ? void 0 : table.initialState.globalFilter); + }; + }, +}; +const RowExpanding = { + getInitialState: (state) => { + return { + expanded: {}, + ...state, + }; + }, + getDefaultOptions: (table) => { + return { + onExpandedChange: makeStateUpdater("expanded", table), + paginateExpandedRows: true, + }; + }, + createTable: (table) => { + let registered = false; + let queued = false; + table._autoResetExpanded = () => { + var _ref, _table$options$autoRe; + if (!registered) { + table._queue(() => { + registered = true; + }); + return; + } + if ((_ref = (_table$options$autoRe = table.options.autoResetAll) != null ? _table$options$autoRe : table.options.autoResetExpanded) != null ? _ref : !table.options.manualExpanding) { + if (queued) return; + queued = true; + table._queue(() => { + table.resetExpanded(); + queued = false; + }); + } + }; + table.setExpanded = (updater) => (table.options.onExpandedChange == null ? void 0 : table.options.onExpandedChange(updater)); + table.toggleAllRowsExpanded = (expanded) => { + if (expanded != null ? expanded : !table.getIsAllRowsExpanded()) { + table.setExpanded(true); + } else { + table.setExpanded({}); + } + }; + table.resetExpanded = (defaultState) => { + var _table$initialState$e, _table$initialState; + table.setExpanded( + defaultState ? {} + : (_table$initialState$e = (_table$initialState = table.initialState) == null ? void 0 : _table$initialState.expanded) != null ? _table$initialState$e + : {}, + ); + }; + table.getCanSomeRowsExpand = () => { + return table.getPrePaginationRowModel().flatRows.some((row) => row.getCanExpand()); + }; + table.getToggleAllRowsExpandedHandler = () => { + return (e) => { + e.persist == null || e.persist(); + table.toggleAllRowsExpanded(); + }; + }; + table.getIsSomeRowsExpanded = () => { + const expanded = table.getState().expanded; + return expanded === true || Object.values(expanded).some(Boolean); + }; + table.getIsAllRowsExpanded = () => { + const expanded = table.getState().expanded; + if (typeof expanded === "boolean") { + return expanded === true; + } + if (!Object.keys(expanded).length) { + return false; + } + if (table.getRowModel().flatRows.some((row) => !row.getIsExpanded())) { + return false; + } + return true; + }; + table.getExpandedDepth = () => { + let maxDepth = 0; + const rowIds = table.getState().expanded === true ? Object.keys(table.getRowModel().rowsById) : Object.keys(table.getState().expanded); + rowIds.forEach((id) => { + const splitId = id.split("."); + maxDepth = Math.max(maxDepth, splitId.length); + }); + return maxDepth; + }; + table.getPreExpandedRowModel = () => table.getSortedRowModel(); + table.getExpandedRowModel = () => { + if (!table._getExpandedRowModel && table.options.getExpandedRowModel) { + table._getExpandedRowModel = table.options.getExpandedRowModel(table); + } + if (table.options.manualExpanding || !table._getExpandedRowModel) { + return table.getPreExpandedRowModel(); + } + return table._getExpandedRowModel(); + }; + }, + createRow: (row, table) => { + row.toggleExpanded = (expanded) => { + table.setExpanded((old) => { + var _expanded; + const exists = old === true ? true : !!(old != null && old[row.id]); + let oldExpanded = {}; + if (old === true) { + Object.keys(table.getRowModel().rowsById).forEach((rowId) => { + oldExpanded[rowId] = true; + }); + } else { + oldExpanded = old; + } + expanded = (_expanded = expanded) != null ? _expanded : !exists; + if (!exists && expanded) { + return { + ...oldExpanded, + [row.id]: true, + }; + } + if (exists && !expanded) { + const { [row.id]: _, ...rest } = oldExpanded; + return rest; + } + return old; + }); + }; + row.getIsExpanded = () => { + var _table$options$getIsR; + const expanded = table.getState().expanded; + return !!((_table$options$getIsR = table.options.getIsRowExpanded == null ? void 0 : table.options.getIsRowExpanded(row)) != null ? + _table$options$getIsR + : expanded === true || (expanded == null ? void 0 : expanded[row.id])); + }; + row.getCanExpand = () => { + var _table$options$getRow, _table$options$enable, _row$subRows; + return (_table$options$getRow = table.options.getRowCanExpand == null ? void 0 : table.options.getRowCanExpand(row)) != null ? + _table$options$getRow + : ((_table$options$enable = table.options.enableExpanding) != null ? _table$options$enable : true) && !!((_row$subRows = row.subRows) != null && _row$subRows.length); + }; + row.getIsAllParentsExpanded = () => { + let isFullyExpanded = true; + let currentRow = row; + while (isFullyExpanded && currentRow.parentId) { + currentRow = table.getRow(currentRow.parentId, true); + isFullyExpanded = currentRow.getIsExpanded(); + } + return isFullyExpanded; + }; + row.getToggleExpandedHandler = () => { + const canExpand = row.getCanExpand(); + return () => { + if (!canExpand) return; + row.toggleExpanded(); + }; + }; + }, +}; +const defaultPageIndex = 0; +const defaultPageSize = 10; +const getDefaultPaginationState = () => ({ + pageIndex: defaultPageIndex, + pageSize: defaultPageSize, +}); +const RowPagination = { + getInitialState: (state) => { + return { + ...state, + pagination: { + ...getDefaultPaginationState(), + ...(state == null ? void 0 : state.pagination), + }, + }; + }, + getDefaultOptions: (table) => { + return { + onPaginationChange: makeStateUpdater("pagination", table), + }; + }, + createTable: (table) => { + let registered = false; + let queued = false; + table._autoResetPageIndex = () => { + var _ref, _table$options$autoRe; + if (!registered) { + table._queue(() => { + registered = true; + }); + return; + } + if ((_ref = (_table$options$autoRe = table.options.autoResetAll) != null ? _table$options$autoRe : table.options.autoResetPageIndex) != null ? _ref : !table.options.manualPagination) { + if (queued) return; + queued = true; + table._queue(() => { + table.resetPageIndex(); + queued = false; + }); + } + }; + table.setPagination = (updater) => { + const safeUpdater = (old) => { + let newState = functionalUpdate(updater, old); + return newState; + }; + return table.options.onPaginationChange == null ? void 0 : table.options.onPaginationChange(safeUpdater); + }; + table.resetPagination = (defaultState) => { + var _table$initialState$p; + table.setPagination( + defaultState ? getDefaultPaginationState() + : (_table$initialState$p = table.initialState.pagination) != null ? _table$initialState$p + : getDefaultPaginationState(), + ); + }; + table.setPageIndex = (updater) => { + table.setPagination((old) => { + let pageIndex = functionalUpdate(updater, old.pageIndex); + const maxPageIndex = typeof table.options.pageCount === "undefined" || table.options.pageCount === -1 ? Number.MAX_SAFE_INTEGER : table.options.pageCount - 1; + pageIndex = Math.max(0, Math.min(pageIndex, maxPageIndex)); + return { + ...old, + pageIndex, + }; + }); + }; + table.resetPageIndex = (defaultState) => { + var _table$initialState$p2, _table$initialState; + table.setPageIndex( + defaultState ? defaultPageIndex + : ( + (_table$initialState$p2 = + (_table$initialState = table.initialState) == null || (_table$initialState = _table$initialState.pagination) == null ? void 0 : _table$initialState.pageIndex) != null + ) ? + _table$initialState$p2 + : defaultPageIndex, + ); + }; + table.resetPageSize = (defaultState) => { + var _table$initialState$p3, _table$initialState2; + table.setPageSize( + defaultState ? defaultPageSize + : ( + (_table$initialState$p3 = + (_table$initialState2 = table.initialState) == null || (_table$initialState2 = _table$initialState2.pagination) == null ? void 0 : _table$initialState2.pageSize) != null + ) ? + _table$initialState$p3 + : defaultPageSize, + ); + }; + table.setPageSize = (updater) => { + table.setPagination((old) => { + const pageSize = Math.max(1, functionalUpdate(updater, old.pageSize)); + const topRowIndex = old.pageSize * old.pageIndex; + const pageIndex = Math.floor(topRowIndex / pageSize); + return { + ...old, + pageIndex, + pageSize, + }; + }); + }; + table.setPageCount = (updater) => + table.setPagination((old) => { + var _table$options$pageCo; + let newPageCount = functionalUpdate(updater, (_table$options$pageCo = table.options.pageCount) != null ? _table$options$pageCo : -1); + if (typeof newPageCount === "number") { + newPageCount = Math.max(-1, newPageCount); + } + return { + ...old, + pageCount: newPageCount, + }; + }); + table.getPageOptions = memo( + () => [table.getPageCount()], + (pageCount) => { + let pageOptions = []; + if (pageCount && pageCount > 0) { + pageOptions = [...new Array(pageCount)].fill(null).map((_, i) => i); + } + return pageOptions; + }, + getMemoOptions(table.options, "debugTable"), + ); + table.getCanPreviousPage = () => table.getState().pagination.pageIndex > 0; + table.getCanNextPage = () => { + const { pageIndex } = table.getState().pagination; + const pageCount = table.getPageCount(); + if (pageCount === -1) { + return true; + } + if (pageCount === 0) { + return false; + } + return pageIndex < pageCount - 1; + }; + table.previousPage = () => { + return table.setPageIndex((old) => old - 1); + }; + table.nextPage = () => { + return table.setPageIndex((old) => { + return old + 1; + }); + }; + table.firstPage = () => { + return table.setPageIndex(0); + }; + table.lastPage = () => { + return table.setPageIndex(table.getPageCount() - 1); + }; + table.getPrePaginationRowModel = () => table.getExpandedRowModel(); + table.getPaginationRowModel = () => { + if (!table._getPaginationRowModel && table.options.getPaginationRowModel) { + table._getPaginationRowModel = table.options.getPaginationRowModel(table); + } + if (table.options.manualPagination || !table._getPaginationRowModel) { + return table.getPrePaginationRowModel(); + } + return table._getPaginationRowModel(); + }; + table.getPageCount = () => { + var _table$options$pageCo2; + return (_table$options$pageCo2 = table.options.pageCount) != null ? _table$options$pageCo2 : Math.ceil(table.getRowCount() / table.getState().pagination.pageSize); + }; + table.getRowCount = () => { + var _table$options$rowCou; + return (_table$options$rowCou = table.options.rowCount) != null ? _table$options$rowCou : table.getPrePaginationRowModel().rows.length; + }; + }, +}; +const getDefaultRowPinningState = () => ({ + top: [], + bottom: [], +}); +const RowPinning = { + getInitialState: (state) => { + return { + rowPinning: getDefaultRowPinningState(), + ...state, + }; + }, + getDefaultOptions: (table) => { + return { + onRowPinningChange: makeStateUpdater("rowPinning", table), + }; + }, + createRow: (row, table) => { + row.pin = (position, includeLeafRows, includeParentRows) => { + const leafRowIds = + includeLeafRows ? + row.getLeafRows().map((_ref) => { + let { id } = _ref; + return id; + }) + : []; + const parentRowIds = + includeParentRows ? + row.getParentRows().map((_ref2) => { + let { id } = _ref2; + return id; + }) + : []; + const rowIds = /* @__PURE__ */ new Set([...parentRowIds, row.id, ...leafRowIds]); + table.setRowPinning((old) => { + var _old$top3, _old$bottom3; + if (position === "bottom") { + var _old$top, _old$bottom; + return { + top: ((_old$top = old == null ? void 0 : old.top) != null ? _old$top : []).filter((d) => !(rowIds != null && rowIds.has(d))), + bottom: [...((_old$bottom = old == null ? void 0 : old.bottom) != null ? _old$bottom : []).filter((d) => !(rowIds != null && rowIds.has(d))), ...Array.from(rowIds)], + }; + } + if (position === "top") { + var _old$top2, _old$bottom2; + return { + top: [...((_old$top2 = old == null ? void 0 : old.top) != null ? _old$top2 : []).filter((d) => !(rowIds != null && rowIds.has(d))), ...Array.from(rowIds)], + bottom: ((_old$bottom2 = old == null ? void 0 : old.bottom) != null ? _old$bottom2 : []).filter((d) => !(rowIds != null && rowIds.has(d))), + }; + } + return { + top: ((_old$top3 = old == null ? void 0 : old.top) != null ? _old$top3 : []).filter((d) => !(rowIds != null && rowIds.has(d))), + bottom: ((_old$bottom3 = old == null ? void 0 : old.bottom) != null ? _old$bottom3 : []).filter((d) => !(rowIds != null && rowIds.has(d))), + }; + }); + }; + row.getCanPin = () => { + var _ref3; + const { enableRowPinning, enablePinning } = table.options; + if (typeof enableRowPinning === "function") { + return enableRowPinning(row); + } + return (_ref3 = enableRowPinning != null ? enableRowPinning : enablePinning) != null ? _ref3 : true; + }; + row.getIsPinned = () => { + const rowIds = [row.id]; + const { top, bottom } = table.getState().rowPinning; + const isTop = rowIds.some((d) => (top == null ? void 0 : top.includes(d))); + const isBottom = rowIds.some((d) => (bottom == null ? void 0 : bottom.includes(d))); + return ( + isTop ? "top" + : isBottom ? "bottom" + : false + ); + }; + row.getPinnedIndex = () => { + var _ref4, _visiblePinnedRowIds$; + const position = row.getIsPinned(); + if (!position) return -1; + const visiblePinnedRowIds = + (_ref4 = position === "top" ? table.getTopRows() : table.getBottomRows()) == null ? + void 0 + : _ref4.map((_ref5) => { + let { id } = _ref5; + return id; + }); + return (_visiblePinnedRowIds$ = visiblePinnedRowIds == null ? void 0 : visiblePinnedRowIds.indexOf(row.id)) != null ? _visiblePinnedRowIds$ : -1; + }; + }, + createTable: (table) => { + table.setRowPinning = (updater) => (table.options.onRowPinningChange == null ? void 0 : table.options.onRowPinningChange(updater)); + table.resetRowPinning = (defaultState) => { + var _table$initialState$r, _table$initialState; + return table.setRowPinning( + defaultState ? getDefaultRowPinningState() + : (_table$initialState$r = (_table$initialState = table.initialState) == null ? void 0 : _table$initialState.rowPinning) != null ? _table$initialState$r + : getDefaultRowPinningState(), + ); + }; + table.getIsSomeRowsPinned = (position) => { + var _pinningState$positio; + const pinningState = table.getState().rowPinning; + if (!position) { + var _pinningState$top, _pinningState$bottom; + return Boolean( + ((_pinningState$top = pinningState.top) == null ? void 0 : _pinningState$top.length) || + ((_pinningState$bottom = pinningState.bottom) == null ? void 0 : _pinningState$bottom.length), + ); + } + return Boolean((_pinningState$positio = pinningState[position]) == null ? void 0 : _pinningState$positio.length); + }; + table._getPinnedRows = (visibleRows, pinnedRowIds, position) => { + var _table$options$keepPi; + const rows = + ( + (_table$options$keepPi = table.options.keepPinnedRows) != null ? _table$options$keepPi : true + ) ? + //get all rows that are pinned even if they would not be otherwise visible + //account for expanded parent rows, but not pagination or filtering + (pinnedRowIds != null ? pinnedRowIds : []).map((rowId) => { + const row = table.getRow(rowId, true); + return row.getIsAllParentsExpanded() ? row : null; + }) + //else get only visible rows that are pinned + : (pinnedRowIds != null ? pinnedRowIds : []).map((rowId) => visibleRows.find((row) => row.id === rowId)); + return rows.filter(Boolean).map((d) => ({ + ...d, + position, + })); + }; + table.getTopRows = memo( + () => [table.getRowModel().rows, table.getState().rowPinning.top], + (allRows, topPinnedRowIds) => table._getPinnedRows(allRows, topPinnedRowIds, "top"), + getMemoOptions(table.options, "debugRows"), + ); + table.getBottomRows = memo( + () => [table.getRowModel().rows, table.getState().rowPinning.bottom], + (allRows, bottomPinnedRowIds) => table._getPinnedRows(allRows, bottomPinnedRowIds, "bottom"), + getMemoOptions(table.options, "debugRows"), + ); + table.getCenterRows = memo( + () => [table.getRowModel().rows, table.getState().rowPinning.top, table.getState().rowPinning.bottom], + (allRows, top, bottom) => { + const topAndBottom = /* @__PURE__ */ new Set([...(top != null ? top : []), ...(bottom != null ? bottom : [])]); + return allRows.filter((d) => !topAndBottom.has(d.id)); + }, + getMemoOptions(table.options, "debugRows"), + ); + }, +}; +const RowSelection = { + getInitialState: (state) => { + return { + rowSelection: {}, + ...state, + }; + }, + getDefaultOptions: (table) => { + return { + onRowSelectionChange: makeStateUpdater("rowSelection", table), + enableRowSelection: true, + enableMultiRowSelection: true, + enableSubRowSelection: true, + // enableGroupingRowSelection: false, + // isAdditiveSelectEvent: (e: unknown) => !!e.metaKey, + // isInclusiveSelectEvent: (e: unknown) => !!e.shiftKey, + }; + }, + createTable: (table) => { + table.setRowSelection = (updater) => (table.options.onRowSelectionChange == null ? void 0 : table.options.onRowSelectionChange(updater)); + table.resetRowSelection = (defaultState) => { + var _table$initialState$r; + return table.setRowSelection( + defaultState ? {} + : (_table$initialState$r = table.initialState.rowSelection) != null ? _table$initialState$r + : {}, + ); + }; + table.toggleAllRowsSelected = (value) => { + table.setRowSelection((old) => { + value = typeof value !== "undefined" ? value : !table.getIsAllRowsSelected(); + const rowSelection = { + ...old, + }; + const preGroupedFlatRows = table.getPreGroupedRowModel().flatRows; + if (value) { + preGroupedFlatRows.forEach((row) => { + if (!row.getCanSelect()) { + return; + } + rowSelection[row.id] = true; + }); + } else { + preGroupedFlatRows.forEach((row) => { + delete rowSelection[row.id]; + }); + } + return rowSelection; + }); + }; + table.toggleAllPageRowsSelected = (value) => + table.setRowSelection((old) => { + const resolvedValue = typeof value !== "undefined" ? value : !table.getIsAllPageRowsSelected(); + const rowSelection = { + ...old, + }; + table.getRowModel().rows.forEach((row) => { + mutateRowIsSelected(rowSelection, row.id, resolvedValue, true, table); + }); + return rowSelection; + }); + table.getPreSelectedRowModel = () => table.getCoreRowModel(); + table.getSelectedRowModel = memo( + () => [table.getState().rowSelection, table.getCoreRowModel()], + (rowSelection, rowModel) => { + if (!Object.keys(rowSelection).length) { + return { + rows: [], + flatRows: [], + rowsById: {}, + }; + } + return selectRowsFn(table, rowModel); + }, + getMemoOptions(table.options, "debugTable"), + ); + table.getFilteredSelectedRowModel = memo( + () => [table.getState().rowSelection, table.getFilteredRowModel()], + (rowSelection, rowModel) => { + if (!Object.keys(rowSelection).length) { + return { + rows: [], + flatRows: [], + rowsById: {}, + }; + } + return selectRowsFn(table, rowModel); + }, + getMemoOptions(table.options, "debugTable"), + ); + table.getGroupedSelectedRowModel = memo( + () => [table.getState().rowSelection, table.getSortedRowModel()], + (rowSelection, rowModel) => { + if (!Object.keys(rowSelection).length) { + return { + rows: [], + flatRows: [], + rowsById: {}, + }; + } + return selectRowsFn(table, rowModel); + }, + getMemoOptions(table.options, "debugTable"), + ); + table.getIsAllRowsSelected = () => { + const preGroupedFlatRows = table.getFilteredRowModel().flatRows; + const { rowSelection } = table.getState(); + let isAllRowsSelected = Boolean(preGroupedFlatRows.length && Object.keys(rowSelection).length); + if (isAllRowsSelected) { + if (preGroupedFlatRows.some((row) => row.getCanSelect() && !rowSelection[row.id])) { + isAllRowsSelected = false; + } + } + return isAllRowsSelected; + }; + table.getIsAllPageRowsSelected = () => { + const paginationFlatRows = table.getPaginationRowModel().flatRows.filter((row) => row.getCanSelect()); + const { rowSelection } = table.getState(); + let isAllPageRowsSelected = !!paginationFlatRows.length; + if (isAllPageRowsSelected && paginationFlatRows.some((row) => !rowSelection[row.id])) { + isAllPageRowsSelected = false; + } + return isAllPageRowsSelected; + }; + table.getIsSomeRowsSelected = () => { + var _table$getState$rowSe; + const totalSelected = Object.keys((_table$getState$rowSe = table.getState().rowSelection) != null ? _table$getState$rowSe : {}).length; + return totalSelected > 0 && totalSelected < table.getFilteredRowModel().flatRows.length; + }; + table.getIsSomePageRowsSelected = () => { + const paginationFlatRows = table.getPaginationRowModel().flatRows; + return table.getIsAllPageRowsSelected() ? false : paginationFlatRows.filter((row) => row.getCanSelect()).some((d) => d.getIsSelected() || d.getIsSomeSelected()); + }; + table.getToggleAllRowsSelectedHandler = () => { + return (e) => { + table.toggleAllRowsSelected(e.target.checked); + }; + }; + table.getToggleAllPageRowsSelectedHandler = () => { + return (e) => { + table.toggleAllPageRowsSelected(e.target.checked); + }; + }; + }, + createRow: (row, table) => { + row.toggleSelected = (value, opts) => { + const isSelected = row.getIsSelected(); + table.setRowSelection((old) => { + var _opts$selectChildren; + value = typeof value !== "undefined" ? value : !isSelected; + if (row.getCanSelect() && isSelected === value) { + return old; + } + const selectedRowIds = { + ...old, + }; + mutateRowIsSelected(selectedRowIds, row.id, value, (_opts$selectChildren = opts == null ? void 0 : opts.selectChildren) != null ? _opts$selectChildren : true, table); + return selectedRowIds; + }); + }; + row.getIsSelected = () => { + const { rowSelection } = table.getState(); + return isRowSelected(row, rowSelection); + }; + row.getIsSomeSelected = () => { + const { rowSelection } = table.getState(); + return isSubRowSelected(row, rowSelection) === "some"; + }; + row.getIsAllSubRowsSelected = () => { + const { rowSelection } = table.getState(); + return isSubRowSelected(row, rowSelection) === "all"; + }; + row.getCanSelect = () => { + var _table$options$enable; + if (typeof table.options.enableRowSelection === "function") { + return table.options.enableRowSelection(row); + } + return (_table$options$enable = table.options.enableRowSelection) != null ? _table$options$enable : true; + }; + row.getCanSelectSubRows = () => { + var _table$options$enable2; + if (typeof table.options.enableSubRowSelection === "function") { + return table.options.enableSubRowSelection(row); + } + return (_table$options$enable2 = table.options.enableSubRowSelection) != null ? _table$options$enable2 : true; + }; + row.getCanMultiSelect = () => { + var _table$options$enable3; + if (typeof table.options.enableMultiRowSelection === "function") { + return table.options.enableMultiRowSelection(row); + } + return (_table$options$enable3 = table.options.enableMultiRowSelection) != null ? _table$options$enable3 : true; + }; + row.getToggleSelectedHandler = () => { + const canSelect = row.getCanSelect(); + return (e) => { + var _target; + if (!canSelect) return; + row.toggleSelected((_target = e.target) == null ? void 0 : _target.checked); + }; + }; + }, +}; +const mutateRowIsSelected = (selectedRowIds, id, value, includeChildren, table) => { + var _row$subRows; + const row = table.getRow(id, true); + if (value) { + if (!row.getCanMultiSelect()) { + Object.keys(selectedRowIds).forEach((key) => delete selectedRowIds[key]); + } + if (row.getCanSelect()) { + selectedRowIds[id] = true; + } + } else { + delete selectedRowIds[id]; + } + if (includeChildren && (_row$subRows = row.subRows) != null && _row$subRows.length && row.getCanSelectSubRows()) { + row.subRows.forEach((row2) => mutateRowIsSelected(selectedRowIds, row2.id, value, includeChildren, table)); + } +}; +function selectRowsFn(table, rowModel) { + const rowSelection = table.getState().rowSelection; + const newSelectedFlatRows = []; + const newSelectedRowsById = {}; + const recurseRows = function (rows, depth) { + return rows + .map((row) => { + var _row$subRows2; + const isSelected = isRowSelected(row, rowSelection); + if (isSelected) { + newSelectedFlatRows.push(row); + newSelectedRowsById[row.id] = row; + } + if ((_row$subRows2 = row.subRows) != null && _row$subRows2.length) { + row = { + ...row, + subRows: recurseRows(row.subRows), + }; + } + if (isSelected) { + return row; + } + }) + .filter(Boolean); + }; + return { + rows: recurseRows(rowModel.rows), + flatRows: newSelectedFlatRows, + rowsById: newSelectedRowsById, + }; +} +function isRowSelected(row, selection) { + var _selection$row$id; + return (_selection$row$id = selection[row.id]) != null ? _selection$row$id : false; +} +function isSubRowSelected(row, selection, table) { + var _row$subRows3; + if (!((_row$subRows3 = row.subRows) != null && _row$subRows3.length)) return false; + let allChildrenSelected = true; + let someSelected = false; + row.subRows.forEach((subRow) => { + if (someSelected && !allChildrenSelected) { + return; + } + if (subRow.getCanSelect()) { + if (isRowSelected(subRow, selection)) { + someSelected = true; + } else { + allChildrenSelected = false; + } + } + if (subRow.subRows && subRow.subRows.length) { + const subRowChildrenSelected = isSubRowSelected(subRow, selection); + if (subRowChildrenSelected === "all") { + someSelected = true; + } else if (subRowChildrenSelected === "some") { + someSelected = true; + allChildrenSelected = false; + } else { + allChildrenSelected = false; + } + } + }); + return ( + allChildrenSelected ? "all" + : someSelected ? "some" + : false + ); +} +const reSplitAlphaNumeric = /([0-9]+)/gm; +const alphanumeric = (rowA, rowB, columnId) => { + return compareAlphanumeric(toString(rowA.getValue(columnId)).toLowerCase(), toString(rowB.getValue(columnId)).toLowerCase()); +}; +const alphanumericCaseSensitive = (rowA, rowB, columnId) => { + return compareAlphanumeric(toString(rowA.getValue(columnId)), toString(rowB.getValue(columnId))); +}; +const text = (rowA, rowB, columnId) => { + return compareBasic(toString(rowA.getValue(columnId)).toLowerCase(), toString(rowB.getValue(columnId)).toLowerCase()); +}; +const textCaseSensitive = (rowA, rowB, columnId) => { + return compareBasic(toString(rowA.getValue(columnId)), toString(rowB.getValue(columnId))); +}; +const datetime = (rowA, rowB, columnId) => { + const a = rowA.getValue(columnId); + const b = rowB.getValue(columnId); + return ( + a > b ? 1 + : a < b ? -1 + : 0 + ); +}; +const basic = (rowA, rowB, columnId) => { + return compareBasic(rowA.getValue(columnId), rowB.getValue(columnId)); +}; +function compareBasic(a, b) { + return ( + a === b ? 0 + : a > b ? 1 + : -1 + ); +} +function toString(a) { + if (typeof a === "number") { + if (isNaN(a) || a === Infinity || a === -Infinity) { + return ""; + } + return String(a); + } + if (typeof a === "string") { + return a; + } + return ""; +} +function compareAlphanumeric(aStr, bStr) { + const a = aStr.split(reSplitAlphaNumeric).filter(Boolean); + const b = bStr.split(reSplitAlphaNumeric).filter(Boolean); + while (a.length && b.length) { + const aa = a.shift(); + const bb = b.shift(); + const an = parseInt(aa, 10); + const bn = parseInt(bb, 10); + const combo = [an, bn].sort(); + if (isNaN(combo[0])) { + if (aa > bb) { + return 1; + } + if (bb > aa) { + return -1; + } + continue; + } + if (isNaN(combo[1])) { + return isNaN(an) ? -1 : 1; + } + if (an > bn) { + return 1; + } + if (bn > an) { + return -1; + } + } + return a.length - b.length; +} +const sortingFns = { + alphanumeric, + alphanumericCaseSensitive, + text, + textCaseSensitive, + datetime, + basic, +}; +const RowSorting = { + getInitialState: (state) => { + return { + sorting: [], + ...state, + }; + }, + getDefaultColumnDef: () => { + return { + sortingFn: "auto", + sortUndefined: 1, + }; + }, + getDefaultOptions: (table) => { + return { + onSortingChange: makeStateUpdater("sorting", table), + isMultiSortEvent: (e) => { + return e.shiftKey; + }, + }; + }, + createColumn: (column, table) => { + column.getAutoSortingFn = () => { + const firstRows = table.getFilteredRowModel().flatRows.slice(10); + let isString = false; + for (const row of firstRows) { + const value = row == null ? void 0 : row.getValue(column.id); + if (Object.prototype.toString.call(value) === "[object Date]") { + return sortingFns.datetime; + } + if (typeof value === "string") { + isString = true; + if (value.split(reSplitAlphaNumeric).length > 1) { + return sortingFns.alphanumeric; + } + } + } + if (isString) { + return sortingFns.text; + } + return sortingFns.basic; + }; + column.getAutoSortDir = () => { + const firstRow = table.getFilteredRowModel().flatRows[0]; + const value = firstRow == null ? void 0 : firstRow.getValue(column.id); + if (typeof value === "string") { + return "asc"; + } + return "desc"; + }; + column.getSortingFn = () => { + var _table$options$sortin, _table$options$sortin2; + if (!column) { + throw new Error(); + } + return ( + isFunction(column.columnDef.sortingFn) ? column.columnDef.sortingFn + : column.columnDef.sortingFn === "auto" ? column.getAutoSortingFn() + : (_table$options$sortin = (_table$options$sortin2 = table.options.sortingFns) == null ? void 0 : _table$options$sortin2[column.columnDef.sortingFn]) != null ? _table$options$sortin + : sortingFns[column.columnDef.sortingFn] + ); + }; + column.toggleSorting = (desc, multi) => { + const nextSortingOrder = column.getNextSortingOrder(); + const hasManualValue = typeof desc !== "undefined" && desc !== null; + table.setSorting((old) => { + const existingSorting = old == null ? void 0 : old.find((d) => d.id === column.id); + const existingIndex = old == null ? void 0 : old.findIndex((d) => d.id === column.id); + let newSorting = []; + let sortAction; + let nextDesc = hasManualValue ? desc : nextSortingOrder === "desc"; + if (old != null && old.length && column.getCanMultiSort() && multi) { + if (existingSorting) { + sortAction = "toggle"; + } else { + sortAction = "add"; + } + } else { + if (old != null && old.length && existingIndex !== old.length - 1) { + sortAction = "replace"; + } else if (existingSorting) { + sortAction = "toggle"; + } else { + sortAction = "replace"; + } + } + if (sortAction === "toggle") { + if (!hasManualValue) { + if (!nextSortingOrder) { + sortAction = "remove"; + } + } + } + if (sortAction === "add") { + var _table$options$maxMul; + newSorting = [ + ...old, + { + id: column.id, + desc: nextDesc, + }, + ]; + newSorting.splice(0, newSorting.length - ((_table$options$maxMul = table.options.maxMultiSortColCount) != null ? _table$options$maxMul : Number.MAX_SAFE_INTEGER)); + } else if (sortAction === "toggle") { + newSorting = old.map((d) => { + if (d.id === column.id) { + return { + ...d, + desc: nextDesc, + }; + } + return d; + }); + } else if (sortAction === "remove") { + newSorting = old.filter((d) => d.id !== column.id); + } else { + newSorting = [ + { + id: column.id, + desc: nextDesc, + }, + ]; + } + return newSorting; + }); + }; + column.getFirstSortDir = () => { + var _ref, _column$columnDef$sor; + const sortDescFirst = + (_ref = (_column$columnDef$sor = column.columnDef.sortDescFirst) != null ? _column$columnDef$sor : table.options.sortDescFirst) != null ? _ref : column.getAutoSortDir() === "desc"; + return sortDescFirst ? "desc" : "asc"; + }; + column.getNextSortingOrder = (multi) => { + var _table$options$enable, _table$options$enable2; + const firstSortDirection = column.getFirstSortDir(); + const isSorted = column.getIsSorted(); + if (!isSorted) { + return firstSortDirection; + } + if ( + isSorted !== firstSortDirection && + ((_table$options$enable = table.options.enableSortingRemoval) != null ? _table$options$enable : true) && // If enableSortRemove, enable in general + (multi ? + (_table$options$enable2 = table.options.enableMultiRemove) != null ? + _table$options$enable2 + : true + : true) + ) { + return false; + } + return isSorted === "desc" ? "asc" : "desc"; + }; + column.getCanSort = () => { + var _column$columnDef$ena, _table$options$enable3; + return ( + ((_column$columnDef$ena = column.columnDef.enableSorting) != null ? _column$columnDef$ena : true) && + ((_table$options$enable3 = table.options.enableSorting) != null ? _table$options$enable3 : true) && + !!column.accessorFn + ); + }; + column.getCanMultiSort = () => { + var _ref2, _column$columnDef$ena2; + return (_ref2 = (_column$columnDef$ena2 = column.columnDef.enableMultiSort) != null ? _column$columnDef$ena2 : table.options.enableMultiSort) != null ? _ref2 : !!column.accessorFn; + }; + column.getIsSorted = () => { + var _table$getState$sorti; + const columnSort = (_table$getState$sorti = table.getState().sorting) == null ? void 0 : _table$getState$sorti.find((d) => d.id === column.id); + return ( + !columnSort ? false + : columnSort.desc ? "desc" + : "asc" + ); + }; + column.getSortIndex = () => { + var _table$getState$sorti2, _table$getState$sorti3; + return (_table$getState$sorti2 = (_table$getState$sorti3 = table.getState().sorting) == null ? void 0 : _table$getState$sorti3.findIndex((d) => d.id === column.id)) != null ? + _table$getState$sorti2 + : -1; + }; + column.clearSorting = () => { + table.setSorting((old) => (old != null && old.length ? old.filter((d) => d.id !== column.id) : [])); + }; + column.getToggleSortingHandler = () => { + const canSort = column.getCanSort(); + return (e) => { + if (!canSort) return; + e.persist == null || e.persist(); + column.toggleSorting == null || + column.toggleSorting( + void 0, + column.getCanMultiSort() ? + table.options.isMultiSortEvent == null ? + void 0 + : table.options.isMultiSortEvent(e) + : false, + ); + }; + }; + }, + createTable: (table) => { + table.setSorting = (updater) => (table.options.onSortingChange == null ? void 0 : table.options.onSortingChange(updater)); + table.resetSorting = (defaultState) => { + var _table$initialState$s, _table$initialState; + table.setSorting( + defaultState ? [] + : (_table$initialState$s = (_table$initialState = table.initialState) == null ? void 0 : _table$initialState.sorting) != null ? _table$initialState$s + : [], + ); + }; + table.getPreSortedRowModel = () => table.getGroupedRowModel(); + table.getSortedRowModel = () => { + if (!table._getSortedRowModel && table.options.getSortedRowModel) { + table._getSortedRowModel = table.options.getSortedRowModel(table); + } + if (table.options.manualSorting || !table._getSortedRowModel) { + return table.getPreSortedRowModel(); + } + return table._getSortedRowModel(); + }; + }, +}; +const builtInFeatures = [ + Headers$1, + ColumnVisibility, + ColumnOrdering, + ColumnPinning, + ColumnFaceting, + ColumnFiltering, + GlobalFaceting, + //depends on ColumnFaceting + GlobalFiltering, + //depends on ColumnFiltering + RowSorting, + ColumnGrouping, + //depends on RowSorting + RowExpanding, + RowPagination, + RowPinning, + RowSelection, + ColumnSizing, +]; +function createTable(options) { + var _options$_features, _options$initialState; + const _features = [...builtInFeatures, ...((_options$_features = options._features) != null ? _options$_features : [])]; + let table = { + _features, + }; + const defaultOptions = table._features.reduce((obj, feature) => { + return Object.assign(obj, feature.getDefaultOptions == null ? void 0 : feature.getDefaultOptions(table)); + }, {}); + const mergeOptions = (options2) => { + if (table.options.mergeOptions) { + return table.options.mergeOptions(defaultOptions, options2); + } + return { + ...defaultOptions, + ...options2, + }; + }; + const coreInitialState = {}; + let initialState = { + ...coreInitialState, + ...((_options$initialState = options.initialState) != null ? _options$initialState : {}), + }; + table._features.forEach((feature) => { + var _feature$getInitialSt; + initialState = (_feature$getInitialSt = feature.getInitialState == null ? void 0 : feature.getInitialState(initialState)) != null ? _feature$getInitialSt : initialState; + }); + const queued = []; + let queuedTimeout = false; + const coreInstance = { + _features, + options: { + ...defaultOptions, + ...options, + }, + initialState, + _queue: (cb) => { + queued.push(cb); + if (!queuedTimeout) { + queuedTimeout = true; + Promise.resolve() + .then(() => { + while (queued.length) { + queued.shift()(); + } + queuedTimeout = false; + }) + .catch((error) => + setTimeout(() => { + throw error; + }), + ); + } + }, + reset: () => { + table.setState(table.initialState); + }, + setOptions: (updater) => { + const newOptions = functionalUpdate(updater, table.options); + table.options = mergeOptions(newOptions); + }, + getState: () => { + return table.options.state; + }, + setState: (updater) => { + table.options.onStateChange == null || table.options.onStateChange(updater); + }, + _getRowId: (row, index, parent) => { + var _table$options$getRow; + return (_table$options$getRow = table.options.getRowId == null ? void 0 : table.options.getRowId(row, index, parent)) != null ? + _table$options$getRow + : `${parent ? [parent.id, index].join(".") : index}`; + }, + getCoreRowModel: () => { + if (!table._getCoreRowModel) { + table._getCoreRowModel = table.options.getCoreRowModel(table); + } + return table._getCoreRowModel(); + }, + // The final calls start at the bottom of the model, + // expanded rows, which then work their way up + getRowModel: () => { + return table.getPaginationRowModel(); + }, + //in next version, we should just pass in the row model as the optional 2nd arg + getRow: (id, searchAll) => { + let row = (searchAll ? table.getPrePaginationRowModel() : table.getRowModel()).rowsById[id]; + if (!row) { + row = table.getCoreRowModel().rowsById[id]; + if (!row) { + throw new Error(); + } + } + return row; + }, + _getDefaultColumnDef: memo( + () => [table.options.defaultColumn], + (defaultColumn) => { + var _defaultColumn; + defaultColumn = (_defaultColumn = defaultColumn) != null ? _defaultColumn : {}; + return { + header: (props) => { + const resolvedColumnDef = props.header.column.columnDef; + if (resolvedColumnDef.accessorKey) { + return resolvedColumnDef.accessorKey; + } + if (resolvedColumnDef.accessorFn) { + return resolvedColumnDef.id; + } + return null; + }, + // footer: props => props.header.column.id, + cell: (props) => { + var _props$renderValue$to, _props$renderValue; + return (_props$renderValue$to = (_props$renderValue = props.renderValue()) == null || _props$renderValue.toString == null ? void 0 : _props$renderValue.toString()) != null ? + _props$renderValue$to + : null; + }, + ...table._features.reduce((obj, feature) => { + return Object.assign(obj, feature.getDefaultColumnDef == null ? void 0 : feature.getDefaultColumnDef()); + }, {}), + ...defaultColumn, + }; + }, + getMemoOptions(options, "debugColumns"), + ), + _getColumnDefs: () => table.options.columns, + getAllColumns: memo( + () => [table._getColumnDefs()], + (columnDefs) => { + const recurseColumns = function (columnDefs2, parent, depth) { + if (depth === void 0) { + depth = 0; + } + return columnDefs2.map((columnDef) => { + const column = createColumn(table, columnDef, depth, parent); + const groupingColumnDef = columnDef; + column.columns = groupingColumnDef.columns ? recurseColumns(groupingColumnDef.columns, column, depth + 1) : []; + return column; + }); + }; + return recurseColumns(columnDefs); + }, + getMemoOptions(options, "debugColumns"), + ), + getAllFlatColumns: memo( + () => [table.getAllColumns()], + (allColumns) => { + return allColumns.flatMap((column) => { + return column.getFlatColumns(); + }); + }, + getMemoOptions(options, "debugColumns"), + ), + _getAllFlatColumnsById: memo( + () => [table.getAllFlatColumns()], + (flatColumns) => { + return flatColumns.reduce((acc, column) => { + acc[column.id] = column; + return acc; + }, {}); + }, + getMemoOptions(options, "debugColumns"), + ), + getAllLeafColumns: memo( + () => [table.getAllColumns(), table._getOrderColumnsFn()], + (allColumns, orderColumns2) => { + let leafColumns = allColumns.flatMap((column) => column.getLeafColumns()); + return orderColumns2(leafColumns); + }, + getMemoOptions(options, "debugColumns"), + ), + getColumn: (columnId) => { + const column = table._getAllFlatColumnsById()[columnId]; + return column; + }, + }; + Object.assign(table, coreInstance); + for (let index = 0; index < table._features.length; index++) { + const feature = table._features[index]; + feature == null || feature.createTable == null || feature.createTable(table); + } + return table; +} +function getCoreRowModel() { + return (table) => + memo( + () => [table.options.data], + (data) => { + const rowModel = { + rows: [], + flatRows: [], + rowsById: {}, + }; + const accessRows = function (originalRows, depth, parentRow) { + if (depth === void 0) { + depth = 0; + } + const rows = []; + for (let i = 0; i < originalRows.length; i++) { + const row = createRow(table, table._getRowId(originalRows[i], i, parentRow), originalRows[i], i, depth, void 0, parentRow == null ? void 0 : parentRow.id); + rowModel.flatRows.push(row); + rowModel.rowsById[row.id] = row; + rows.push(row); + if (table.options.getSubRows) { + var _row$originalSubRows; + row.originalSubRows = table.options.getSubRows(originalRows[i], i); + if ((_row$originalSubRows = row.originalSubRows) != null && _row$originalSubRows.length) { + row.subRows = accessRows(row.originalSubRows, depth + 1, row); + } + } + } + return rows; + }; + rowModel.rows = accessRows(data); + return rowModel; + }, + getMemoOptions(table.options, "debugTable", "getRowModel", () => table._autoResetPageIndex()), + ); +} +function expandRows(rowModel) { + const expandedRows = []; + const handleRow = (row) => { + var _row$subRows; + expandedRows.push(row); + if ((_row$subRows = row.subRows) != null && _row$subRows.length && row.getIsExpanded()) { + row.subRows.forEach(handleRow); + } + }; + rowModel.rows.forEach(handleRow); + return { + rows: expandedRows, + flatRows: rowModel.flatRows, + rowsById: rowModel.rowsById, + }; +} +function filterRows(rows, filterRowImpl, table) { + if (table.options.filterFromLeafRows) { + return filterRowModelFromLeafs(rows, filterRowImpl, table); + } + return filterRowModelFromRoot(rows, filterRowImpl, table); +} +function filterRowModelFromLeafs(rowsToFilter, filterRow, table) { + var _table$options$maxLea; + const newFilteredFlatRows = []; + const newFilteredRowsById = {}; + const maxDepth = (_table$options$maxLea = table.options.maxLeafRowFilterDepth) != null ? _table$options$maxLea : 100; + const recurseFilterRows = function (rowsToFilter2, depth) { + if (depth === void 0) { + depth = 0; + } + const rows = []; + for (let i = 0; i < rowsToFilter2.length; i++) { + var _row$subRows; + let row = rowsToFilter2[i]; + const newRow = createRow(table, row.id, row.original, row.index, row.depth, void 0, row.parentId); + newRow.columnFilters = row.columnFilters; + if ((_row$subRows = row.subRows) != null && _row$subRows.length && depth < maxDepth) { + newRow.subRows = recurseFilterRows(row.subRows, depth + 1); + row = newRow; + if (filterRow(row) && !newRow.subRows.length) { + rows.push(row); + newFilteredRowsById[row.id] = row; + newFilteredFlatRows.push(row); + continue; + } + if (filterRow(row) || newRow.subRows.length) { + rows.push(row); + newFilteredRowsById[row.id] = row; + newFilteredFlatRows.push(row); + continue; + } + } else { + row = newRow; + if (filterRow(row)) { + rows.push(row); + newFilteredRowsById[row.id] = row; + newFilteredFlatRows.push(row); + } + } + } + return rows; + }; + return { + rows: recurseFilterRows(rowsToFilter), + flatRows: newFilteredFlatRows, + rowsById: newFilteredRowsById, + }; +} +function filterRowModelFromRoot(rowsToFilter, filterRow, table) { + var _table$options$maxLea2; + const newFilteredFlatRows = []; + const newFilteredRowsById = {}; + const maxDepth = (_table$options$maxLea2 = table.options.maxLeafRowFilterDepth) != null ? _table$options$maxLea2 : 100; + const recurseFilterRows = function (rowsToFilter2, depth) { + if (depth === void 0) { + depth = 0; + } + const rows = []; + for (let i = 0; i < rowsToFilter2.length; i++) { + let row = rowsToFilter2[i]; + const pass = filterRow(row); + if (pass) { + var _row$subRows2; + if ((_row$subRows2 = row.subRows) != null && _row$subRows2.length && depth < maxDepth) { + const newRow = createRow(table, row.id, row.original, row.index, row.depth, void 0, row.parentId); + newRow.subRows = recurseFilterRows(row.subRows, depth + 1); + row = newRow; + } + rows.push(row); + newFilteredFlatRows.push(row); + newFilteredRowsById[row.id] = row; + } + } + return rows; + }; + return { + rows: recurseFilterRows(rowsToFilter), + flatRows: newFilteredFlatRows, + rowsById: newFilteredRowsById, + }; +} +function getFilteredRowModel() { + return (table) => + memo( + () => [table.getPreFilteredRowModel(), table.getState().columnFilters, table.getState().globalFilter], + (rowModel, columnFilters, globalFilter) => { + if (!rowModel.rows.length || (!(columnFilters != null && columnFilters.length) && !globalFilter)) { + for (let i = 0; i < rowModel.flatRows.length; i++) { + rowModel.flatRows[i].columnFilters = {}; + rowModel.flatRows[i].columnFiltersMeta = {}; + } + return rowModel; + } + const resolvedColumnFilters = []; + const resolvedGlobalFilters = []; + (columnFilters != null ? columnFilters : []).forEach((d) => { + var _filterFn$resolveFilt; + const column = table.getColumn(d.id); + if (!column) { + return; + } + const filterFn = column.getFilterFn(); + if (!filterFn) { + return; + } + resolvedColumnFilters.push({ + id: d.id, + filterFn, + resolvedValue: (_filterFn$resolveFilt = filterFn.resolveFilterValue == null ? void 0 : filterFn.resolveFilterValue(d.value)) != null ? _filterFn$resolveFilt : d.value, + }); + }); + const filterableIds = (columnFilters != null ? columnFilters : []).map((d) => d.id); + const globalFilterFn = table.getGlobalFilterFn(); + const globallyFilterableColumns = table.getAllLeafColumns().filter((column) => column.getCanGlobalFilter()); + if (globalFilter && globalFilterFn && globallyFilterableColumns.length) { + filterableIds.push("__global__"); + globallyFilterableColumns.forEach((column) => { + var _globalFilterFn$resol; + resolvedGlobalFilters.push({ + id: column.id, + filterFn: globalFilterFn, + resolvedValue: + (_globalFilterFn$resol = globalFilterFn.resolveFilterValue == null ? void 0 : globalFilterFn.resolveFilterValue(globalFilter)) != null ? + _globalFilterFn$resol + : globalFilter, + }); + }); + } + let currentColumnFilter; + let currentGlobalFilter; + for (let j = 0; j < rowModel.flatRows.length; j++) { + const row = rowModel.flatRows[j]; + row.columnFilters = {}; + if (resolvedColumnFilters.length) { + for (let i = 0; i < resolvedColumnFilters.length; i++) { + currentColumnFilter = resolvedColumnFilters[i]; + const id = currentColumnFilter.id; + row.columnFilters[id] = currentColumnFilter.filterFn(row, id, currentColumnFilter.resolvedValue, (filterMeta) => { + row.columnFiltersMeta[id] = filterMeta; + }); + } + } + if (resolvedGlobalFilters.length) { + for (let i = 0; i < resolvedGlobalFilters.length; i++) { + currentGlobalFilter = resolvedGlobalFilters[i]; + const id = currentGlobalFilter.id; + if ( + currentGlobalFilter.filterFn(row, id, currentGlobalFilter.resolvedValue, (filterMeta) => { + row.columnFiltersMeta[id] = filterMeta; + }) + ) { + row.columnFilters.__global__ = true; + break; + } + } + if (row.columnFilters.__global__ !== true) { + row.columnFilters.__global__ = false; + } + } + } + const filterRowsImpl = (row) => { + for (let i = 0; i < filterableIds.length; i++) { + if (row.columnFilters[filterableIds[i]] === false) { + return false; + } + } + return true; + }; + return filterRows(rowModel.rows, filterRowsImpl, table); + }, + getMemoOptions(table.options, "debugTable", "getFilteredRowModel", () => table._autoResetPageIndex()), + ); +} +function getPaginationRowModel(opts) { + return (table) => + memo( + () => [table.getState().pagination, table.getPrePaginationRowModel(), table.options.paginateExpandedRows ? void 0 : table.getState().expanded], + (pagination, rowModel) => { + if (!rowModel.rows.length) { + return rowModel; + } + const { pageSize, pageIndex } = pagination; + let { rows, flatRows, rowsById } = rowModel; + const pageStart = pageSize * pageIndex; + const pageEnd = pageStart + pageSize; + rows = rows.slice(pageStart, pageEnd); + let paginatedRowModel; + if (!table.options.paginateExpandedRows) { + paginatedRowModel = expandRows({ + rows, + flatRows, + rowsById, + }); + } else { + paginatedRowModel = { + rows, + flatRows, + rowsById, + }; + } + paginatedRowModel.flatRows = []; + const handleRow = (row) => { + paginatedRowModel.flatRows.push(row); + if (row.subRows.length) { + row.subRows.forEach(handleRow); + } + }; + paginatedRowModel.rows.forEach(handleRow); + return paginatedRowModel; + }, + getMemoOptions(table.options, "debugTable"), + ); +} +function getSortedRowModel() { + return (table) => + memo( + () => [table.getState().sorting, table.getPreSortedRowModel()], + (sorting, rowModel) => { + if (!rowModel.rows.length || !(sorting != null && sorting.length)) { + return rowModel; + } + const sortingState = table.getState().sorting; + const sortedFlatRows = []; + const availableSorting = sortingState.filter((sort) => { + var _table$getColumn; + return (_table$getColumn = table.getColumn(sort.id)) == null ? void 0 : _table$getColumn.getCanSort(); + }); + const columnInfoById = {}; + availableSorting.forEach((sortEntry) => { + const column = table.getColumn(sortEntry.id); + if (!column) return; + columnInfoById[sortEntry.id] = { + sortUndefined: column.columnDef.sortUndefined, + invertSorting: column.columnDef.invertSorting, + sortingFn: column.getSortingFn(), + }; + }); + const sortData = (rows) => { + const sortedData = rows.map((row) => ({ + ...row, + })); + sortedData.sort((rowA, rowB) => { + for (let i = 0; i < availableSorting.length; i += 1) { + var _sortEntry$desc; + const sortEntry = availableSorting[i]; + const columnInfo = columnInfoById[sortEntry.id]; + const sortUndefined = columnInfo.sortUndefined; + const isDesc = (_sortEntry$desc = sortEntry == null ? void 0 : sortEntry.desc) != null ? _sortEntry$desc : false; + let sortInt = 0; + if (sortUndefined) { + const aValue = rowA.getValue(sortEntry.id); + const bValue = rowB.getValue(sortEntry.id); + const aUndefined = aValue === void 0; + const bUndefined = bValue === void 0; + if (aUndefined || bUndefined) { + if (sortUndefined === "first") return aUndefined ? -1 : 1; + if (sortUndefined === "last") return aUndefined ? 1 : -1; + sortInt = + aUndefined && bUndefined ? 0 + : aUndefined ? sortUndefined + : -sortUndefined; + } + } + if (sortInt === 0) { + sortInt = columnInfo.sortingFn(rowA, rowB, sortEntry.id); + } + if (sortInt !== 0) { + if (isDesc) { + sortInt *= -1; + } + if (columnInfo.invertSorting) { + sortInt *= -1; + } + return sortInt; + } + } + return rowA.index - rowB.index; + }); + sortedData.forEach((row) => { + var _row$subRows; + sortedFlatRows.push(row); + if ((_row$subRows = row.subRows) != null && _row$subRows.length) { + row.subRows = sortData(row.subRows); + } + }); + return sortedData; + }; + return { + rows: sortData(rowModel.rows), + flatRows: sortedFlatRows, + rowsById: rowModel.rowsById, + }; + }, + getMemoOptions(table.options, "debugTable", "getSortedRowModel", () => table._autoResetPageIndex()), + ); +} + +/** + * react-table + * + * Copyright (c) TanStack + * + * This source code is licensed under the MIT license found in the + * LICENSE.md file in the root directory of this source tree. + * + * @license MIT + */ +const React$1 = await importShared("react"); + +// + +/** + * If rendering headers, cells, or footers with custom markup, use flexRender instead of `cell.getValue()` or `cell.renderValue()`. + */ +function flexRender(Comp, props) { + return ( + !Comp ? null + : isReactComponent(Comp) ? /*#__PURE__*/ React$1.createElement(Comp, props) + : Comp + ); +} +function isReactComponent(component) { + return isClassComponent(component) || typeof component === "function" || isExoticComponent(component); +} +function isClassComponent(component) { + return ( + typeof component === "function" && + (() => { + const proto = Object.getPrototypeOf(component); + return proto.prototype && proto.prototype.isReactComponent; + })() + ); +} +function isExoticComponent(component) { + return typeof component === "object" && typeof component.$$typeof === "symbol" && ["react.memo", "react.forward_ref"].includes(component.$$typeof.description); +} +function useReactTable(options) { + // Compose in the generic options to the user options + const resolvedOptions = { + state: {}, + // Dummy state + onStateChange: () => {}, + // noop + renderFallbackValue: null, + ...options, + }; + + // Create a new table and store it in state + const [tableRef] = React$1.useState(() => ({ + current: createTable(resolvedOptions), + })); + + // By default, manage table state here using the table's initial state + const [state, setState] = React$1.useState(() => tableRef.current.initialState); + + // Compose the default state above with any user state. This will allow the user + // to only control a subset of the state if desired. + tableRef.current.setOptions((prev) => ({ + ...prev, + ...options, + state: { + ...state, + ...options.state, + }, + // Similarly, we'll maintain both our internal state and any user-provided + // state. + onStateChange: (updater) => { + setState(updater); + options.onStateChange == null || options.onStateChange(updater); + }, + })); + return tableRef.current; +} + +var dist = { exports: {} }; + +var sister; +var hasRequiredSister; + +function requireSister() { + if (hasRequiredSister) return sister; + hasRequiredSister = 1; + + var Sister; + + /** + * @link https://github.com/gajus/sister for the canonical source repository + * @license https://github.com/gajus/sister/blob/master/LICENSE BSD 3-Clause + */ + Sister = function () { + var sister = {}, + events = {}; + + /** + * @name handler + * @function + * @param {Object} data Event data. + */ + + /** + * @param {String} name Event name. + * @param {handler} handler + * @return {listener} + */ + sister.on = function (name, handler) { + var listener = { name: name, handler: handler }; + events[name] = events[name] || []; + events[name].unshift(listener); + return listener; + }; + + /** + * @param {listener} + */ + sister.off = function (listener) { + var index = events[listener.name].indexOf(listener); + + if (index !== -1) { + events[listener.name].splice(index, 1); + } + }; + + /** + * @param {String} name Event name. + * @param {Object} data Event data. + */ + sister.trigger = function (name, data) { + var listeners = events[name], + i; + + if (listeners) { + i = listeners.length; + while (i--) { + listeners[i].handler(data); + } + } + }; + + return sister; + }; + + sister = Sister; + return sister; +} + +var YouTubePlayer$1 = { exports: {} }; + +var browser = { exports: {} }; + +/** + * Helpers. + */ + +var ms; +var hasRequiredMs; + +function requireMs() { + if (hasRequiredMs) return ms; + hasRequiredMs = 1; + var s = 1000; + var m = s * 60; + var h = m * 60; + var d = h * 24; + var w = d * 7; + var y = d * 365.25; + + /** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + + ms = function (val, options) { + options = options || {}; + var type = typeof val; + if (type === "string" && val.length > 0) { + return parse(val); + } else if (type === "number" && isFinite(val)) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error("val is not a non-empty string or a valid number. val=" + JSON.stringify(val)); + }; + + /** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + + function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(str); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || "ms").toLowerCase(); + switch (type) { + case "years": + case "year": + case "yrs": + case "yr": + case "y": + return n * y; + case "weeks": + case "week": + case "w": + return n * w; + case "days": + case "day": + case "d": + return n * d; + case "hours": + case "hour": + case "hrs": + case "hr": + case "h": + return n * h; + case "minutes": + case "minute": + case "mins": + case "min": + case "m": + return n * m; + case "seconds": + case "second": + case "secs": + case "sec": + case "s": + return n * s; + case "milliseconds": + case "millisecond": + case "msecs": + case "msec": + case "ms": + return n; + default: + return undefined; + } + } + + /** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + + function fmtShort(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return Math.round(ms / d) + "d"; + } + if (msAbs >= h) { + return Math.round(ms / h) + "h"; + } + if (msAbs >= m) { + return Math.round(ms / m) + "m"; + } + if (msAbs >= s) { + return Math.round(ms / s) + "s"; + } + return ms + "ms"; + } + + /** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + + function fmtLong(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return plural(ms, msAbs, d, "day"); + } + if (msAbs >= h) { + return plural(ms, msAbs, h, "hour"); + } + if (msAbs >= m) { + return plural(ms, msAbs, m, "minute"); + } + if (msAbs >= s) { + return plural(ms, msAbs, s, "second"); + } + return ms + " ms"; + } + + /** + * Pluralization helper. + */ + + function plural(ms, msAbs, n, name) { + var isPlural = msAbs >= n * 1.5; + return Math.round(ms / n) + " " + name + (isPlural ? "s" : ""); + } + return ms; +} + +var common; +var hasRequiredCommon; + +function requireCommon() { + if (hasRequiredCommon) return common; + hasRequiredCommon = 1; + /** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + */ + + function setup(env) { + createDebug.debug = createDebug; + createDebug.default = createDebug; + createDebug.coerce = coerce; + createDebug.disable = disable; + createDebug.enable = enable; + createDebug.enabled = enabled; + createDebug.humanize = requireMs(); + createDebug.destroy = destroy; + + Object.keys(env).forEach((key) => { + createDebug[key] = env[key]; + }); + + /** + * The currently active debug mode names, and names to skip. + */ + + createDebug.names = []; + createDebug.skips = []; + + /** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + createDebug.formatters = {}; + + /** + * Selects a color for a debug namespace + * @param {String} namespace The namespace string for the debug instance to be colored + * @return {Number|String} An ANSI color code for the given namespace + * @api private + */ + function selectColor(namespace) { + let hash = 0; + + for (let i = 0; i < namespace.length; i++) { + hash = (hash << 5) - hash + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return createDebug.colors[Math.abs(hash) % createDebug.colors.length]; + } + createDebug.selectColor = selectColor; + + /** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + function createDebug(namespace) { + let prevTime; + let enableOverride = null; + let namespacesCache; + let enabledCache; + + function debug(...args) { + // Disabled? + if (!debug.enabled) { + return; + } + + const self = debug; + + // Set `diff` timestamp + const curr = Number(new Date()); + const ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + args[0] = createDebug.coerce(args[0]); + + if (typeof args[0] !== "string") { + // Anything else let's inspect with %O + args.unshift("%O"); + } + + // Apply any `formatters` transformations + let index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { + // If we encounter an escaped % then don't increase the array index + if (match === "%%") { + return "%"; + } + index++; + const formatter = createDebug.formatters[format]; + if (typeof formatter === "function") { + const val = args[index]; + match = formatter.call(self, val); + + // Now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + // Apply env-specific formatting (colors, etc.) + createDebug.formatArgs.call(self, args); + + const logFn = self.log || createDebug.log; + logFn.apply(self, args); + } + + debug.namespace = namespace; + debug.useColors = createDebug.useColors(); + debug.color = createDebug.selectColor(namespace); + debug.extend = extend; + debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release. + + Object.defineProperty(debug, "enabled", { + enumerable: true, + configurable: false, + get: () => { + if (enableOverride !== null) { + return enableOverride; + } + if (namespacesCache !== createDebug.namespaces) { + namespacesCache = createDebug.namespaces; + enabledCache = createDebug.enabled(namespace); + } + + return enabledCache; + }, + set: (v) => { + enableOverride = v; + }, + }); + + // Env-specific initialization logic for debug instances + if (typeof createDebug.init === "function") { + createDebug.init(debug); + } + + return debug; + } + + function extend(namespace, delimiter) { + const newDebug = createDebug(this.namespace + (typeof delimiter === "undefined" ? ":" : delimiter) + namespace); + newDebug.log = this.log; + return newDebug; + } + + /** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + function enable(namespaces) { + createDebug.save(namespaces); + createDebug.namespaces = namespaces; + + createDebug.names = []; + createDebug.skips = []; + + const split = (typeof namespaces === "string" ? namespaces : "").trim().replace(/\s+/g, ",").split(",").filter(Boolean); + + for (const ns of split) { + if (ns[0] === "-") { + createDebug.skips.push(ns.slice(1)); + } else { + createDebug.names.push(ns); + } + } + } + + /** + * Checks if the given string matches a namespace template, honoring + * asterisks as wildcards. + * + * @param {String} search + * @param {String} template + * @return {Boolean} + */ + function matchesTemplate(search, template) { + let searchIndex = 0; + let templateIndex = 0; + let starIndex = -1; + let matchIndex = 0; + + while (searchIndex < search.length) { + if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === "*")) { + // Match character or proceed with wildcard + if (template[templateIndex] === "*") { + starIndex = templateIndex; + matchIndex = searchIndex; + templateIndex++; // Skip the '*' + } else { + searchIndex++; + templateIndex++; + } + } else if (starIndex !== -1) { + // eslint-disable-line no-negated-condition + // Backtrack to the last '*' and try to match more characters + templateIndex = starIndex + 1; + matchIndex++; + searchIndex = matchIndex; + } else { + return false; // No match + } + } + + // Handle trailing '*' in template + while (templateIndex < template.length && template[templateIndex] === "*") { + templateIndex++; + } + + return templateIndex === template.length; + } + + /** + * Disable debug output. + * + * @return {String} namespaces + * @api public + */ + function disable() { + const namespaces = [...createDebug.names, ...createDebug.skips.map((namespace) => "-" + namespace)].join(","); + createDebug.enable(""); + return namespaces; + } + + /** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + function enabled(name) { + for (const skip of createDebug.skips) { + if (matchesTemplate(name, skip)) { + return false; + } + } + + for (const ns of createDebug.names) { + if (matchesTemplate(name, ns)) { + return true; + } + } + + return false; + } + + /** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + function coerce(val) { + if (val instanceof Error) { + return val.stack || val.message; + } + return val; + } + + /** + * XXX DO NOT USE. This is a temporary stub function. + * XXX It WILL be removed in the next major release. + */ + function destroy() { + console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."); + } + + createDebug.enable(createDebug.load()); + + return createDebug; + } + + common = setup; + return common; +} + +var hasRequiredBrowser; + +function requireBrowser() { + if (hasRequiredBrowser) return browser.exports; + hasRequiredBrowser = 1; + (function (module, exports) { + var define_process_env_default = {}; + exports.formatArgs = formatArgs; + exports.save = save; + exports.load = load; + exports.useColors = useColors; + exports.storage = localstorage(); + exports.destroy = /* @__PURE__ */ (() => { + let warned = false; + return () => { + if (!warned) { + warned = true; + console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."); + } + }; + })(); + exports.colors = [ + "#0000CC", + "#0000FF", + "#0033CC", + "#0033FF", + "#0066CC", + "#0066FF", + "#0099CC", + "#0099FF", + "#00CC00", + "#00CC33", + "#00CC66", + "#00CC99", + "#00CCCC", + "#00CCFF", + "#3300CC", + "#3300FF", + "#3333CC", + "#3333FF", + "#3366CC", + "#3366FF", + "#3399CC", + "#3399FF", + "#33CC00", + "#33CC33", + "#33CC66", + "#33CC99", + "#33CCCC", + "#33CCFF", + "#6600CC", + "#6600FF", + "#6633CC", + "#6633FF", + "#66CC00", + "#66CC33", + "#9900CC", + "#9900FF", + "#9933CC", + "#9933FF", + "#99CC00", + "#99CC33", + "#CC0000", + "#CC0033", + "#CC0066", + "#CC0099", + "#CC00CC", + "#CC00FF", + "#CC3300", + "#CC3333", + "#CC3366", + "#CC3399", + "#CC33CC", + "#CC33FF", + "#CC6600", + "#CC6633", + "#CC9900", + "#CC9933", + "#CCCC00", + "#CCCC33", + "#FF0000", + "#FF0033", + "#FF0066", + "#FF0099", + "#FF00CC", + "#FF00FF", + "#FF3300", + "#FF3333", + "#FF3366", + "#FF3399", + "#FF33CC", + "#FF33FF", + "#FF6600", + "#FF6633", + "#FF9900", + "#FF9933", + "#FFCC00", + "#FFCC33", + ]; + function useColors() { + if (typeof window !== "undefined" && window.process && (window.process.type === "renderer" || window.process.__nwjs)) { + return true; + } + if (typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { + return false; + } + let m; + return ( + (typeof document !== "undefined" && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || // Is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== "undefined" && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || // Is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== "undefined" && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31) || // Double check webkit in userAgent just in case we are in a worker + (typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)) + ); + } + function formatArgs(args) { + args[0] = (this.useColors ? "%c" : "") + this.namespace + (this.useColors ? " %c" : " ") + args[0] + (this.useColors ? "%c " : " ") + "+" + module.exports.humanize(this.diff); + if (!this.useColors) { + return; + } + const c = "color: " + this.color; + args.splice(1, 0, c, "color: inherit"); + let index = 0; + let lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, (match) => { + if (match === "%%") { + return; + } + index++; + if (match === "%c") { + lastC = index; + } + }); + args.splice(lastC, 0, c); + } + exports.log = console.debug || console.log || (() => {}); + function save(namespaces) { + try { + if (namespaces) { + exports.storage.setItem("debug", namespaces); + } else { + exports.storage.removeItem("debug"); + } + } catch (error) {} + } + function load() { + let r; + try { + r = exports.storage.getItem("debug") || exports.storage.getItem("DEBUG"); + } catch (error) {} + if (!r && typeof process !== "undefined" && "env" in process) { + r = define_process_env_default.DEBUG; + } + return r; + } + function localstorage() { + try { + return localStorage; + } catch (error) {} + } + module.exports = requireCommon()(exports); + const { formatters } = module.exports; + formatters.j = function (v) { + try { + return JSON.stringify(v); + } catch (error) { + return "[UnexpectedJSONParseError]: " + error.message; + } + }; + })(browser, browser.exports); + return browser.exports; +} + +var FunctionStateMap = { exports: {} }; + +var PlayerStates = { exports: {} }; + +var hasRequiredPlayerStates; + +function requirePlayerStates() { + if (hasRequiredPlayerStates) return PlayerStates.exports; + hasRequiredPlayerStates = 1; + (function (module, exports) { + Object.defineProperty(exports, "__esModule", { + value: true, + }); + exports.default = { + BUFFERING: 3, + ENDED: 0, + PAUSED: 2, + PLAYING: 1, + UNSTARTED: -1, + VIDEO_CUED: 5, + }; + module.exports = exports["default"]; + })(PlayerStates, PlayerStates.exports); + return PlayerStates.exports; +} + +var hasRequiredFunctionStateMap; + +function requireFunctionStateMap() { + if (hasRequiredFunctionStateMap) return FunctionStateMap.exports; + hasRequiredFunctionStateMap = 1; + (function (module, exports) { + Object.defineProperty(exports, "__esModule", { + value: true, + }); + + var _PlayerStates = requirePlayerStates(); + + var _PlayerStates2 = _interopRequireDefault(_PlayerStates); + + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; + } + + exports.default = { + pauseVideo: { + acceptableStates: [_PlayerStates2.default.ENDED, _PlayerStates2.default.PAUSED], + stateChangeRequired: false, + }, + playVideo: { + acceptableStates: [_PlayerStates2.default.ENDED, _PlayerStates2.default.PLAYING], + stateChangeRequired: false, + }, + seekTo: { + acceptableStates: [_PlayerStates2.default.ENDED, _PlayerStates2.default.PLAYING, _PlayerStates2.default.PAUSED], + stateChangeRequired: true, + + // TRICKY: `seekTo` may not cause a state change if no buffering is + // required. + // eslint-disable-next-line unicorn/numeric-separators-style + timeout: 3000, + }, + }; + module.exports = exports["default"]; + })(FunctionStateMap, FunctionStateMap.exports); + return FunctionStateMap.exports; +} + +var eventNames = { exports: {} }; + +var hasRequiredEventNames; + +function requireEventNames() { + if (hasRequiredEventNames) return eventNames.exports; + hasRequiredEventNames = 1; + (function (module, exports) { + Object.defineProperty(exports, "__esModule", { + value: true, + }); + + /** + * @see https://developers.google.com/youtube/iframe_api_reference#Events + * `volumeChange` is not officially supported but seems to work + * it emits an object: `{volume: 82.6923076923077, muted: false}` + */ + exports.default = ["ready", "stateChange", "playbackQualityChange", "playbackRateChange", "error", "apiChange", "volumeChange"]; + module.exports = exports["default"]; + })(eventNames, eventNames.exports); + return eventNames.exports; +} + +var functionNames = { exports: {} }; + +var hasRequiredFunctionNames; + +function requireFunctionNames() { + if (hasRequiredFunctionNames) return functionNames.exports; + hasRequiredFunctionNames = 1; + (function (module, exports) { + Object.defineProperty(exports, "__esModule", { + value: true, + }); + + /** + * @see https://developers.google.com/youtube/iframe_api_reference#Functions + */ + exports.default = [ + "cueVideoById", + "loadVideoById", + "cueVideoByUrl", + "loadVideoByUrl", + "playVideo", + "pauseVideo", + "stopVideo", + "getVideoLoadedFraction", + "cuePlaylist", + "loadPlaylist", + "nextVideo", + "previousVideo", + "playVideoAt", + "setShuffle", + "setLoop", + "getPlaylist", + "getPlaylistIndex", + "setOption", + "mute", + "unMute", + "isMuted", + "setVolume", + "getVolume", + "seekTo", + "getPlayerState", + "getPlaybackRate", + "setPlaybackRate", + "getAvailablePlaybackRates", + "getPlaybackQuality", + "setPlaybackQuality", + "getAvailableQualityLevels", + "getCurrentTime", + "getDuration", + "removeEventListener", + "getVideoUrl", + "getVideoEmbedCode", + "getOptions", + "getOption", + "addEventListener", + "destroy", + "setSize", + "getIframe", + "getSphericalProperties", + "setSphericalProperties", + ]; + module.exports = exports["default"]; + })(functionNames, functionNames.exports); + return functionNames.exports; +} + +var hasRequiredYouTubePlayer; + +function requireYouTubePlayer() { + if (hasRequiredYouTubePlayer) return YouTubePlayer$1.exports; + hasRequiredYouTubePlayer = 1; + (function (module, exports) { + Object.defineProperty(exports, "__esModule", { + value: true, + }); + + var _debug = requireBrowser(); + + var _debug2 = _interopRequireDefault(_debug); + + var _FunctionStateMap = requireFunctionStateMap(); + + var _FunctionStateMap2 = _interopRequireDefault(_FunctionStateMap); + + var _eventNames = requireEventNames(); + + var _eventNames2 = _interopRequireDefault(_eventNames); + + var _functionNames = requireFunctionNames(); + + var _functionNames2 = _interopRequireDefault(_functionNames); + + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; + } + + /* eslint-disable promise/prefer-await-to-then */ + + const debug = (0, _debug2.default)("youtube-player"); + + const YouTubePlayer = {}; + + /** + * Construct an object that defines an event handler for all of the YouTube + * player events. Proxy captured events through an event emitter. + * + * @todo Capture event parameters. + * @see https://developers.google.com/youtube/iframe_api_reference#Events + */ + YouTubePlayer.proxyEvents = (emitter) => { + const events = {}; + + for (const eventName of _eventNames2.default) { + const onEventName = "on" + eventName.slice(0, 1).toUpperCase() + eventName.slice(1); + + events[onEventName] = (event) => { + debug('event "%s"', onEventName, event); + + emitter.trigger(eventName, event); + }; + } + + return events; + }; + + /** + * Delays player API method execution until player state is ready. + * + * @todo Proxy all of the methods using Object.keys. + * @todo See TRICKY below. + * @param playerAPIReady Promise that resolves when player is ready. + * @param strictState A flag designating whether or not to wait for + * an acceptable state when calling supported functions. + * @returns {object} + */ + YouTubePlayer.promisifyPlayer = (playerAPIReady, strictState = false) => { + const functions = {}; + + for (const functionName of _functionNames2.default) { + if (strictState && _FunctionStateMap2.default[functionName]) { + functions[functionName] = (...args) => { + return playerAPIReady.then((player) => { + const stateInfo = _FunctionStateMap2.default[functionName]; + const playerState = player.getPlayerState(); + + // eslint-disable-next-line no-warning-comments + // TODO: Just spread the args into the function once Babel is fixed: + // https://github.com/babel/babel/issues/4270 + // + // eslint-disable-next-line prefer-spread + const value = player[functionName].apply(player, args); + + // TRICKY: For functions like `seekTo`, a change in state must be + // triggered given that the resulting state could match the initial + // state. + if ( + stateInfo.stateChangeRequired || + // eslint-disable-next-line no-extra-parens + (Array.isArray(stateInfo.acceptableStates) && !stateInfo.acceptableStates.includes(playerState)) + ) { + return new Promise((resolve) => { + const onPlayerStateChange = () => { + const playerStateAfterChange = player.getPlayerState(); + + let timeout; + + if (typeof stateInfo.timeout === "number") { + timeout = setTimeout(() => { + player.removeEventListener("onStateChange", onPlayerStateChange); + + resolve(); + }, stateInfo.timeout); + } + + if (Array.isArray(stateInfo.acceptableStates) && stateInfo.acceptableStates.includes(playerStateAfterChange)) { + player.removeEventListener("onStateChange", onPlayerStateChange); + + clearTimeout(timeout); + + resolve(); + } + }; + + player.addEventListener("onStateChange", onPlayerStateChange); + }).then(() => { + return value; + }); + } + + return value; + }); + }; + } else { + functions[functionName] = (...args) => { + return playerAPIReady.then((player) => { + // eslint-disable-next-line no-warning-comments + // TODO: Just spread the args into the function once Babel is fixed: + // https://github.com/babel/babel/issues/4270 + // + // eslint-disable-next-line prefer-spread + return player[functionName].apply(player, args); + }); + }; + } + } + + return functions; + }; + + exports.default = YouTubePlayer; + module.exports = exports["default"]; + })(YouTubePlayer$1, YouTubePlayer$1.exports); + return YouTubePlayer$1.exports; +} + +var loadYouTubeIframeApi = { exports: {} }; + +var loadScript; +var hasRequiredLoadScript; + +function requireLoadScript() { + if (hasRequiredLoadScript) return loadScript; + hasRequiredLoadScript = 1; + loadScript = function load(src, opts, cb) { + var head = document.head || document.getElementsByTagName("head")[0]; + var script = document.createElement("script"); + + if (typeof opts === "function") { + cb = opts; + opts = {}; + } + + opts = opts || {}; + cb = cb || function () {}; + + script.type = opts.type || "text/javascript"; + script.charset = opts.charset || "utf8"; + script.async = "async" in opts ? !!opts.async : true; + script.src = src; + + if (opts.attrs) { + setAttributes(script, opts.attrs); + } + + if (opts.text) { + script.text = "" + opts.text; + } + + var onend = "onload" in script ? stdOnEnd : ieOnEnd; + onend(script, cb); + + // some good legacy browsers (firefox) fail the 'in' detection above + // so as a fallback we always set onload + // old IE will ignore this and new IE will set onload + if (!script.onload) { + stdOnEnd(script, cb); + } + + head.appendChild(script); + }; + + function setAttributes(script, attrs) { + for (var attr in attrs) { + script.setAttribute(attr, attrs[attr]); + } + } + + function stdOnEnd(script, cb) { + script.onload = function () { + this.onerror = this.onload = null; + cb(null, script); + }; + script.onerror = function () { + // this.onload = null here is necessary + // because even IE9 works not like others + this.onerror = this.onload = null; + cb(new Error("Failed to load " + this.src), script); + }; + } + + function ieOnEnd(script, cb) { + script.onreadystatechange = function () { + if (this.readyState != "complete" && this.readyState != "loaded") return; + this.onreadystatechange = null; + cb(null, script); // there is no way to catch loading errors in IE8 + }; + } + return loadScript; +} + +var hasRequiredLoadYouTubeIframeApi; + +function requireLoadYouTubeIframeApi() { + if (hasRequiredLoadYouTubeIframeApi) return loadYouTubeIframeApi.exports; + hasRequiredLoadYouTubeIframeApi = 1; + (function (module, exports) { + Object.defineProperty(exports, "__esModule", { + value: true, + }); + + var _loadScript = requireLoadScript(); + + var _loadScript2 = _interopRequireDefault(_loadScript); + + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; + } + + exports.default = (emitter) => { + /** + * A promise that is resolved when window.onYouTubeIframeAPIReady is called. + * The promise is resolved with a reference to window.YT object. + */ + const iframeAPIReady = new Promise((resolve) => { + if (window.YT && window.YT.Player && window.YT.Player instanceof Function) { + resolve(window.YT); + + return; + } else { + const protocol = window.location.protocol === "http:" ? "http:" : "https:"; + + (0, _loadScript2.default)(protocol + "//www.youtube.com/iframe_api", (error) => { + if (error) { + emitter.trigger("error", error); + } + }); + } + + const previous = window.onYouTubeIframeAPIReady; + + // The API will call this function when page has finished downloading + // the JavaScript for the player API. + window.onYouTubeIframeAPIReady = () => { + if (previous) { + previous(); + } + + resolve(window.YT); + }; + }); + + return iframeAPIReady; + }; + + module.exports = exports["default"]; + })(loadYouTubeIframeApi, loadYouTubeIframeApi.exports); + return loadYouTubeIframeApi.exports; +} + +var hasRequiredDist; + +function requireDist() { + if (hasRequiredDist) return dist.exports; + hasRequiredDist = 1; + (function (module, exports) { + Object.defineProperty(exports, "__esModule", { + value: true, + }); + + var _sister = requireSister(); + + var _sister2 = _interopRequireDefault(_sister); + + var _YouTubePlayer = requireYouTubePlayer(); + + var _YouTubePlayer2 = _interopRequireDefault(_YouTubePlayer); + + var _loadYouTubeIframeApi = requireLoadYouTubeIframeApi(); + + var _loadYouTubeIframeApi2 = _interopRequireDefault(_loadYouTubeIframeApi); + + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; + } + + /** + * @typedef YT.Player + * @see https://developers.google.com/youtube/iframe_api_reference + */ + + /** + * @see https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player + */ + let youtubeIframeAPI; + + /** + * A factory function used to produce an instance of YT.Player and queue function calls and proxy events of the resulting object. + * + * @param maybeElementId Either An existing YT.Player instance, + * the DOM element or the id of the HTML element where the API will insert an