@hexclave/next 1.0.24 → 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/components-page/hexclave-handler-client.js +5 -4
  2. package/dist/components-page/hexclave-handler-client.js.map +1 -1
  3. package/dist/dev-tool/dev-tool-core.d.ts.map +1 -1
  4. package/dist/dev-tool/dev-tool-core.js +4 -68
  5. package/dist/dev-tool/dev-tool-core.js.map +1 -1
  6. package/dist/esm/components-page/hexclave-handler-client.js +5 -4
  7. package/dist/esm/components-page/hexclave-handler-client.js.map +1 -1
  8. package/dist/esm/dev-tool/dev-tool-core.d.ts.map +1 -1
  9. package/dist/esm/dev-tool/dev-tool-core.js +4 -68
  10. package/dist/esm/dev-tool/dev-tool-core.js.map +1 -1
  11. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +7 -0
  12. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  13. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js +35 -1
  14. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
  15. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -0
  16. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  17. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +18 -11
  18. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  19. package/dist/esm/lib/hexclave-app/apps/implementations/common.js +1 -1
  20. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.d.ts.map +1 -1
  21. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.js +2 -1
  22. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.js.map +1 -1
  23. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.test.js +26 -0
  24. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.test.js.map +1 -1
  25. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.d.ts +7 -1
  26. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.d.ts.map +1 -1
  27. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.js +11 -1
  28. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.js.map +1 -1
  29. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.test.js +43 -0
  30. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.test.js.map +1 -1
  31. package/dist/esm/lib/hexclave-app/apps/interfaces/admin-app.d.ts +2 -1
  32. package/dist/esm/lib/hexclave-app/apps/interfaces/admin-app.d.ts.map +1 -1
  33. package/dist/esm/lib/hexclave-app/apps/interfaces/admin-app.js.map +1 -1
  34. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts +1 -0
  35. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts.map +1 -1
  36. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  37. package/dist/esm/lib/hexclave-app/index.d.ts +2 -1
  38. package/dist/esm/lib/hexclave-app/plan-usage/index.d.ts +27 -0
  39. package/dist/esm/lib/hexclave-app/plan-usage/index.d.ts.map +1 -0
  40. package/dist/esm/lib/hexclave-app/plan-usage/index.js +1 -0
  41. package/dist/esm/lib/hexclave-app/projects/index.d.ts +7 -0
  42. package/dist/esm/lib/hexclave-app/projects/index.d.ts.map +1 -1
  43. package/dist/esm/lib/hexclave-app/projects/index.js.map +1 -1
  44. package/dist/esm/pushed-config-error-overlay/index.d.ts +7 -0
  45. package/dist/esm/pushed-config-error-overlay/index.d.ts.map +1 -0
  46. package/dist/esm/pushed-config-error-overlay/index.js +464 -0
  47. package/dist/esm/pushed-config-error-overlay/index.js.map +1 -0
  48. package/dist/index.d.ts +2 -1
  49. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +7 -0
  50. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  51. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js +35 -1
  52. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
  53. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -0
  54. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  55. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +18 -11
  56. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  57. package/dist/lib/hexclave-app/apps/implementations/common.js +1 -1
  58. package/dist/lib/hexclave-app/apps/implementations/event-tracker.d.ts.map +1 -1
  59. package/dist/lib/hexclave-app/apps/implementations/event-tracker.js +1 -0
  60. package/dist/lib/hexclave-app/apps/implementations/event-tracker.js.map +1 -1
  61. package/dist/lib/hexclave-app/apps/implementations/event-tracker.test.js +26 -0
  62. package/dist/lib/hexclave-app/apps/implementations/event-tracker.test.js.map +1 -1
  63. package/dist/lib/hexclave-app/apps/implementations/session-replay.d.ts +7 -1
  64. package/dist/lib/hexclave-app/apps/implementations/session-replay.d.ts.map +1 -1
  65. package/dist/lib/hexclave-app/apps/implementations/session-replay.js +11 -0
  66. package/dist/lib/hexclave-app/apps/implementations/session-replay.js.map +1 -1
  67. package/dist/lib/hexclave-app/apps/implementations/session-replay.test.js +43 -0
  68. package/dist/lib/hexclave-app/apps/implementations/session-replay.test.js.map +1 -1
  69. package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts +2 -1
  70. package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts.map +1 -1
  71. package/dist/lib/hexclave-app/apps/interfaces/admin-app.js.map +1 -1
  72. package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts +1 -0
  73. package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts.map +1 -1
  74. package/dist/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  75. package/dist/lib/hexclave-app/index.d.ts +2 -1
  76. package/dist/lib/hexclave-app/plan-usage/index.d.ts +27 -0
  77. package/dist/lib/hexclave-app/plan-usage/index.d.ts.map +1 -0
  78. package/dist/lib/hexclave-app/plan-usage/index.js +0 -0
  79. package/dist/lib/hexclave-app/projects/index.d.ts +7 -0
  80. package/dist/lib/hexclave-app/projects/index.d.ts.map +1 -1
  81. package/dist/lib/hexclave-app/projects/index.js.map +1 -1
  82. package/dist/pushed-config-error-overlay/index.d.ts +7 -0
  83. package/dist/pushed-config-error-overlay/index.d.ts.map +1 -0
  84. package/dist/pushed-config-error-overlay/index.js +466 -0
  85. package/dist/pushed-config-error-overlay/index.js.map +1 -0
  86. package/package.json +4 -4
  87. package/src/components-page/hexclave-handler-client.tsx +5 -4
  88. package/src/dev-tool/dev-tool-core.ts +5 -59
  89. package/src/lib/hexclave-app/apps/implementations/admin-app-impl.ts +44 -1
  90. package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +20 -11
  91. package/src/lib/hexclave-app/apps/implementations/event-tracker.test.ts +33 -0
  92. package/src/lib/hexclave-app/apps/implementations/event-tracker.ts +6 -1
  93. package/src/lib/hexclave-app/apps/implementations/session-replay.test.ts +52 -0
  94. package/src/lib/hexclave-app/apps/implementations/session-replay.ts +20 -0
  95. package/src/lib/hexclave-app/apps/interfaces/admin-app.ts +2 -0
  96. package/src/lib/hexclave-app/apps/interfaces/client-app.ts +1 -0
  97. package/src/lib/hexclave-app/index.ts +8 -0
  98. package/src/lib/hexclave-app/plan-usage/index.ts +29 -0
  99. package/src/lib/hexclave-app/projects/index.ts +3 -0
  100. package/src/pushed-config-error-overlay/index.ts +548 -0
@@ -638,11 +638,6 @@ function createOverviewTab(app: StackClientApp<true>): TabResult {
638
638
 
639
639
  const actions = h('div', { className: 'sdt-ov-actions' });
640
640
  const toast = h('div', { className: 'sdt-ov-toast', style: { display: 'none' } });
641
- const emailRow = h('div', { className: 'sdt-ov-email-input' });
642
- const emailInput = h('input', { type: 'email', placeholder: 'Sign in as email\u2026' }) as HTMLInputElement;
643
- const emailBtn = h('button', null);
644
- setHtml(emailBtn, '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>');
645
- emailRow.append(emailInput, emailBtn);
646
641
 
647
642
  function isBestEffortOverviewError(error: unknown) {
648
643
  if (error instanceof DOMException && error.name === 'AbortError') {
@@ -704,15 +699,14 @@ function createOverviewTab(app: StackClientApp<true>): TabResult {
704
699
  });
705
700
  actions.append(signOutBtn, randomBtn);
706
701
  } else {
707
- const quickBtn = h('button', { className: 'sdt-ov-btn sdt-ov-btn-primary sdt-ov-btn-wide' }, loading ? 'Working\u2026' : 'Quick Sign In');
702
+ const quickBtn = h('button', { className: 'sdt-ov-btn sdt-ov-btn-primary sdt-ov-btn-wide' }, loading ? 'Working\u2026' : 'Quick Sign Up');
708
703
  quickBtn.disabled = loading;
709
704
  quickBtn.addEventListener('click', () => {
710
705
  runAsynchronously(doQuickSignIn());
711
706
  });
712
707
  actions.appendChild(quickBtn);
713
708
  }
714
- emailInput.placeholder = currentUser ? 'Switch to email\u2026' : 'Sign in as email\u2026';
715
- actions.appendChild(emailRow);
709
+
716
710
  }
717
711
 
718
712
  async function doQuickSignIn() {
@@ -744,54 +738,6 @@ function createOverviewTab(app: StackClientApp<true>): TabResult {
744
738
  await refreshUser();
745
739
  }
746
740
 
747
- async function doSignInAs(targetEmail: string) {
748
- if (!targetEmail.trim()) return;
749
- if (!isLocalhost(window.location.href)) {
750
- showToast('Quick sign-in is only available on localhost', 'error');
751
- return;
752
- }
753
- loading = true;
754
- rebuildActions();
755
- const trimmed = targetEmail.trim();
756
- try {
757
- const signInResult = await app.signInWithCredential({ email: trimmed, password: trimmed, noRedirect: true });
758
- if (signInResult.status === 'ok') {
759
- showToast(`Signed in as ${trimmed}`, 'success');
760
- emailInput.value = '';
761
- loading = false;
762
- await refreshUser();
763
- return;
764
- }
765
- const signUpResult = await app.signUpWithCredential({ email: trimmed, password: trimmed, noRedirect: true } as any);
766
- if (signUpResult.status === 'error') {
767
- showToast(`Failed: ${signUpResult.error.message}`, 'error');
768
- loading = false;
769
- rebuildActions();
770
- return;
771
- }
772
- const retryResult = await app.signInWithCredential({ email: trimmed, password: trimmed, noRedirect: true });
773
- if (retryResult.status === 'error') {
774
- showToast(`Sign in failed: ${retryResult.error.message}`, 'error');
775
- } else {
776
- showToast(`Signed in as ${trimmed}`, 'success');
777
- emailInput.value = '';
778
- }
779
- } catch (e: any) {
780
- showToast(e.message || 'Unknown error', 'error');
781
- }
782
- loading = false;
783
- await refreshUser();
784
- }
785
-
786
- emailBtn.addEventListener('click', () => {
787
- runAsynchronously(doSignInAs(emailInput.value));
788
- });
789
- emailInput.addEventListener('keydown', (e) => {
790
- if (e.key === 'Enter') {
791
- runAsynchronously(doSignInAs(emailInput.value));
792
- }
793
- });
794
-
795
741
  heroCard.append(actions, toast);
796
742
 
797
743
  // ── Auth methods card ──────────────────────────────────────────────────────
@@ -855,7 +801,7 @@ function createOverviewTab(app: StackClientApp<true>): TabResult {
855
801
  function buildChecklist() {
856
802
  checksCard.innerHTML = '';
857
803
  const currentUserCheck = hasPersistentTokenStore
858
- ? { ok: !!currentUser, label: 'Sign in a test user', hint: 'Use \u201cQuick Sign In\u201d above \u2192' }
804
+ ? { ok: !!currentUser, label: 'Sign in a test user', hint: 'Use \u201cQuick Sign Up\u201d above \u2192' }
859
805
  : { ok: true, label: 'Current-user tools unavailable', hint: null };
860
806
  const checks = [
861
807
  { ok: !!projectId && projectId !== 'default', label: 'Project configured', hint: null },
@@ -925,7 +871,7 @@ function createOverviewTab(app: StackClientApp<true>): TabResult {
925
871
  } else {
926
872
  avatar.textContent = initials;
927
873
  }
928
- userName.textContent = currentUser.displayName || 'Anonymous';
874
+ userName.textContent = currentUser.displayName || '(No display name)';
929
875
  userEmail.textContent = currentUser.primaryEmail || 'No email';
930
876
  authIndicator.style.display = '';
931
877
  } else {
@@ -1905,7 +1851,7 @@ function createSupportTab(app: StackClientApp<true>): HTMLElement {
1905
1851
  function createComponentsTab(app: StackClientApp<true>): HTMLElement {
1906
1852
  const container = h('div', { className: 'sdt-pg-layout' });
1907
1853
  const apiBaseUrl = resolveApiBaseUrl(app);
1908
- const urls = app.urls;
1854
+ const urls = app[hexclaveAppInternalsSymbol].getUrls();
1909
1855
  const urlOptions: HandlerUrlOptions = app[hexclaveAppInternalsSymbol].getConstructorOptions().urls ?? {};
1910
1856
 
1911
1857
  const PAGE_ENTRIES: { key: keyof HandlerUrls; label: string }[] = [
@@ -24,6 +24,7 @@ import { EmailConfig, hexclaveAppInternalsSymbol } from "../../common";
24
24
  import { AdminEmailTemplate } from "../../email-templates";
25
25
  import { InternalApiKey, InternalApiKeyBase, InternalApiKeyBaseCrudRead, InternalApiKeyCreateOptions, InternalApiKeyFirstView, internalApiKeyCreateOptionsToCrud } from "../../internal-api-keys";
26
26
  import { AdminProjectPermission, AdminProjectPermissionDefinition, AdminProjectPermissionDefinitionCreateOptions, AdminProjectPermissionDefinitionUpdateOptions, AdminTeamPermission, AdminTeamPermissionDefinition, AdminTeamPermissionDefinitionCreateOptions, AdminTeamPermissionDefinitionUpdateOptions, adminProjectPermissionDefinitionCreateOptionsToCrud, adminProjectPermissionDefinitionUpdateOptionsToCrud, adminTeamPermissionDefinitionCreateOptionsToCrud, adminTeamPermissionDefinitionUpdateOptionsToCrud } from "../../permissions";
27
+ import type { PlanUsage } from "../../plan-usage";
27
28
  import { AdminOwnedProject, AdminProject, AdminProjectUpdateOptions, PushConfigOptions, adminProjectUpdateOptionsToCrud } from "../../projects";
28
29
  import type { AdminSessionReplay, AdminSessionReplayChunk, ListSessionReplayChunksOptions, ListSessionReplayChunksResult, ListSessionReplaysOptions, ListSessionReplaysResult, SessionReplayAllEventsResult } from "../../session-replays";
29
30
  import { ManagedEmailProviderListItem, ManagedEmailProviderSetupResult, ManagedEmailProviderStatus, EmailOutboxUpdateOptions, StackAdminApp, StackAdminAppConstructorOptions } from "../interfaces/admin-app";
@@ -37,6 +38,7 @@ import { PushedConfigSource } from "../../projects";
37
38
  import { useAsyncCache } from "./common"; // THIS_LINE_PLATFORM react-like
38
39
 
39
40
  type BranchConfigSourceApi = yup.InferType<typeof branchConfigSourceSchema>;
41
+ type PlanUsageResponse = Awaited<ReturnType<HexclaveAdminInterface["getPlanUsage"]>>;
40
42
  /**
41
43
  * Converts a PushedConfigSource (SDK camelCase) to BranchConfigSourceApi (API snake_case).
42
44
  */
@@ -79,6 +81,9 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
79
81
  private readonly _adminProjectCache = createCache(async () => {
80
82
  return await this._interface.getProject();
81
83
  });
84
+ private readonly _planUsageCache = createCache(async () => {
85
+ return await this._interface.getPlanUsage();
86
+ });
82
87
  private readonly _internalApiKeysCache = createCache(async () => {
83
88
  const res = await this._interface.listInternalApiKeys();
84
89
  return res;
@@ -196,10 +201,17 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
196
201
  isDevelopmentEnvironment: data.is_development_environment,
197
202
  ownerTeamId: data.owner_team_id,
198
203
  onboardingStatus: data.onboarding_status,
204
+ onboardingState: data.onboarding_state ?? null,
199
205
  logoUrl: data.logo_url,
200
206
  logoFullUrl: data.logo_full_url,
201
207
  logoDarkModeUrl: data.logo_dark_mode_url,
202
208
  logoFullDarkModeUrl: data.logo_full_dark_mode_url,
209
+ pushedConfigError: data.pushed_config_error == null ? null : {
210
+ message: data.pushed_config_error.message,
211
+ },
212
+ configWarnings: data.config_warnings.map((warning) => ({
213
+ message: warning.message,
214
+ })),
203
215
  config: {
204
216
  signUpEnabled: data.config.sign_up_enabled,
205
217
  credentialEnabled: data.config.credential_enabled,
@@ -331,6 +343,28 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
331
343
  };
332
344
  }
333
345
 
346
+ _planUsageFromCrud(data: PlanUsageResponse): PlanUsage {
347
+ return {
348
+ ownerTeamId: data.owner_team_id,
349
+ ownerTeamDisplayName: data.owner_team_display_name,
350
+ planId: data.plan_id,
351
+ planDisplayName: data.plan_display_name,
352
+ periodStart: new Date(data.period_start_millis),
353
+ periodEnd: new Date(data.period_end_millis),
354
+ nextPlanId: data.next_plan_id,
355
+ rows: data.rows.map((row) => ({
356
+ itemId: row.item_id,
357
+ displayName: row.display_name,
358
+ kind: row.kind,
359
+ used: row.used,
360
+ limit: row.limit,
361
+ remaining: row.remaining,
362
+ overage: row.overage,
363
+ isUnlimited: row.is_unlimited,
364
+ })),
365
+ };
366
+ }
367
+
334
368
  override async getProject(): Promise<AdminProject> {
335
369
  return this._adminProjectFromCrud(
336
370
  Result.orThrow(await this._adminProjectCache.getOrWait([], "write-only")),
@@ -346,6 +380,15 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
346
380
  ), [crud]);
347
381
  }
348
382
 
383
+ async getPlanUsage(): Promise<PlanUsage> {
384
+ return this._planUsageFromCrud(Result.orThrow(await this._planUsageCache.getOrWait([], "write-only")));
385
+ }
386
+
387
+ usePlanUsage(): PlanUsage {
388
+ const crud = useAsyncCache(this._planUsageCache, [], "adminApp.usePlanUsage()");
389
+ return useMemo(() => this._planUsageFromCrud(crud), [crud]);
390
+ }
391
+
349
392
  protected _createInternalApiKeyBaseFromCrud(data: InternalApiKeyBaseCrudRead): InternalApiKeyBase {
350
393
  const app = this;
351
394
  return {
@@ -1140,7 +1183,7 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
1140
1183
  }
1141
1184
 
1142
1185
  async getStripeAccountInfo(): Promise<null | { account_id: string, charges_enabled: boolean, details_submitted: boolean, payouts_enabled: boolean }> {
1143
- return await this._interface.getStripeAccountInfo();
1186
+ return Result.orThrow(await this._stripeAccountInfoCache.getOrWait([], "write-only"));
1144
1187
  }
1145
1188
 
1146
1189
  useStripeAccountInfo(): { account_id: string, charges_enabled: boolean, details_submitted: boolean, payouts_enabled: boolean } | null {
@@ -2,8 +2,7 @@
2
2
  //===========================================
3
3
  // THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template
4
4
  //===========================================
5
- import { WebAuthnError, startAuthentication, startRegistration } from "@simplewebauthn/browser";
6
- import { KnownError, KnownErrors, HexclaveClientInterface } from "@hexclave/shared";
5
+ import { HexclaveClientInterface, KnownError, KnownErrors } from "@hexclave/shared";
7
6
  import type { RequestListener } from "@hexclave/shared/dist/interface/client-interface";
8
7
  import { ContactChannelsCrud } from "@hexclave/shared/dist/interface/crud/contact-channels";
9
8
  import { CurrentUserCrud } from "@hexclave/shared/dist/interface/crud/current-user";
@@ -41,14 +40,15 @@ import type { TurnstileAction } from "@hexclave/shared/dist/utils/turnstile";
41
40
  import { BotChallengeExecutionFailedError, BotChallengeUserCancelledError, withBotChallengeFlow } from "@hexclave/shared/dist/utils/turnstile-flow";
42
41
  import { createUrlIfValid, getRelativePart, isRelative } from "@hexclave/shared/dist/utils/urls";
43
42
  import { generateUuid } from "@hexclave/shared/dist/utils/uuids";
43
+ import { WebAuthnError, startAuthentication, startRegistration } from "@simplewebauthn/browser";
44
44
  import * as cookie from "cookie";
45
45
  import * as NextNavigationUnscrambled from "next/navigation"; // import the entire module to get around some static compiler warnings emitted by Next.js in some cases | THIS_LINE_PLATFORM next
46
46
  import React, { useCallback, useMemo } from "react"; // THIS_LINE_PLATFORM react-like
47
47
  import type * as yup from "yup";
48
+ import { envVars } from "../../../../generated/env";
48
49
  import { constructRedirectUrl } from "../../../../utils/url";
49
50
  import { callOAuthCallback, getNewOAuthProviderOrScopeUrl } from "../../../auth";
50
51
  import { CookieHelper, createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookie, deleteCookieClient, getCookieClient, isSecure as isSecureCookieContext, saveVerifierAndState, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie";
51
- import { envVars } from "../../../../generated/env";
52
52
  import { ApiKey, ApiKeyCreationOptions, ApiKeyUpdateOptions, apiKeyCreationOptionsToCrud } from "../../api-keys";
53
53
  import { ConvexCtx, GetCurrentPartialUserOptions, GetCurrentUserOptions, HandlerUrlOptions, HandlerUrls, OAuthScopesOnSignIn, RedirectMethod, RedirectToOptions, RequestLike, ResolvedHandlerUrls, TokenStoreInit, hexclaveAppInternalsSymbol } from "../../common";
54
54
  import { DeprecatedOAuthConnection, OAuthConnection } from "../../connected-accounts";
@@ -72,6 +72,7 @@ import { AnalyticsOptions, SessionRecorder, analyticsOptionsFromJson, analyticsO
72
72
  import { useAsyncCache } from "./common";
73
73
  import { mountClickmapOverlay } from "../../../../clickmap";
74
74
  import { mountDevTool } from "../../../../dev-tool";
75
+ import { mountPushedConfigErrorOverlay } from "../../../../pushed-config-error-overlay";
75
76
 
76
77
  let isReactServer = false;
77
78
  import * as sc from "@hexclave/sc";
@@ -354,7 +355,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
354
355
  {
355
356
  provider,
356
357
  redirectUrl: this._getOAuthCallbackRedirectUri(),
357
- errorRedirectUrl: this.urls.error,
358
+ errorRedirectUrl: this._getUrls().error,
358
359
  providerScope: mergeScopeStrings(scopeString, (this._oauthScopesOnSignIn[provider as ProviderType] ?? []).join(" ")),
359
360
  },
360
361
  session,
@@ -557,7 +558,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
557
558
  {
558
559
  provider: options.providerId,
559
560
  redirectUrl: this._getOAuthCallbackRedirectUri(),
560
- errorRedirectUrl: this.urls.error,
561
+ errorRedirectUrl: this._getUrls().error,
561
562
  providerScope: mergeScopeStrings(options.scope || "", (this._oauthScopesOnSignIn[options.providerId] ?? []).join(" ")),
562
563
  },
563
564
  options.session,
@@ -741,6 +742,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
741
742
  // when a dashboard-minted token is handed over, so the listener is
742
743
  // mounted unconditionally (the heavy UI is lazy-loaded on demand).
743
744
  mountClickmapOverlay(this as any);
745
+ mountPushedConfigErrorOverlay(this as any);
744
746
  }
745
747
  }
746
748
 
@@ -862,7 +864,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
862
864
 
863
865
  protected _getOAuthCallbackRedirectUri(): string {
864
866
  if (!this._isOAuthCallbackUrlHosted()) {
865
- return this.urls.oauthCallback;
867
+ return this._getUrls().oauthCallback;
866
868
  }
867
869
  if (typeof window === "undefined") {
868
870
  throw new HexclaveAssertionError("Hosted OAuth callback URLs require a browser environment to use the current URL as the redirect URI");
@@ -1629,6 +1631,12 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
1629
1631
  return {
1630
1632
  id: crud.id,
1631
1633
  displayName: crud.display_name,
1634
+ pushedConfigError: crud.pushed_config_error == null ? null : {
1635
+ message: crud.pushed_config_error.message,
1636
+ },
1637
+ configWarnings: crud.config_warnings.map((warning) => ({
1638
+ message: warning.message,
1639
+ })),
1632
1640
  config: {
1633
1641
  signUpEnabled: crud.config.sign_up_enabled,
1634
1642
  credentialEnabled: crud.config.credential_enabled,
@@ -3016,13 +3024,13 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
3016
3024
  async redirectToMfa(options?: RedirectToOptions) { return await this._redirectToHandler("mfa", options); }
3017
3025
 
3018
3026
  async sendForgotPasswordEmail(email: string, options?: { callbackUrl?: string }): Promise<Result<undefined, KnownErrors["UserNotFound"]>> {
3019
- return await this._interface.sendForgotPasswordEmail(email, options?.callbackUrl ?? constructRedirectUrl(this.urls.passwordReset, "callbackUrl"));
3027
+ return await this._interface.sendForgotPasswordEmail(email, options?.callbackUrl ?? constructRedirectUrl(this._getUrls().passwordReset, "callbackUrl"));
3020
3028
  }
3021
3029
 
3022
3030
  async sendMagicLinkEmail(email: string, options?: {
3023
3031
  callbackUrl?: string,
3024
3032
  }): Promise<Result<{ nonce: string }, KnownErrors["RedirectUrlNotWhitelisted"] | KnownErrors["BotChallengeFailed"]>> {
3025
- const callbackUrl = options?.callbackUrl ?? constructRedirectUrl(this.urls.magicLinkCallback, "callbackUrl");
3033
+ const callbackUrl = options?.callbackUrl ?? constructRedirectUrl(this._getUrls().magicLinkCallback, "callbackUrl");
3026
3034
  return await this._executeResultWithBotChallengeFlow({
3027
3035
  action: "send_magic_link_email",
3028
3036
  execute: async (challenge) => {
@@ -3311,7 +3319,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
3311
3319
  return await this._interface.authorizeOAuth({
3312
3320
  provider,
3313
3321
  redirectUrl: constructRedirectUrl(this._getOAuthCallbackRedirectUri(), "redirectUrl"),
3314
- errorRedirectUrl: constructRedirectUrl(this.urls.error, "errorRedirectUrl"),
3322
+ errorRedirectUrl: constructRedirectUrl(this._getUrls().error, "errorRedirectUrl"),
3315
3323
  afterCallbackRedirectUrl,
3316
3324
  type: "authenticate",
3317
3325
  providerScope: this._oauthScopesOnSignIn[provider]?.join(" "),
@@ -3431,7 +3439,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
3431
3439
  }
3432
3440
  this._ensurePersistentTokenStore();
3433
3441
  const session = await this._getSession();
3434
- const emailVerificationRedirectUrl = options.noVerificationCallback ? undefined : options.verificationCallbackUrl ?? constructRedirectUrl(this.urls.emailVerification, "verificationCallbackUrl");
3442
+ const emailVerificationRedirectUrl = options.noVerificationCallback ? undefined : options.verificationCallbackUrl ?? constructRedirectUrl(this._getUrls().emailVerification, "verificationCallbackUrl");
3435
3443
 
3436
3444
  const executeSignUp = async (challenge: { token?: string, phase?: "invisible" | "visible", unavailable?: true }) => {
3437
3445
  let result = await this._interface.signUpWithCredential(
@@ -3594,7 +3602,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
3594
3602
 
3595
3603
  // Step 2: Open the browser for the user to authenticate and display the verification code
3596
3604
  const url = buildCliAuthConfirmUrl({
3597
- cliAuthConfirmUrl: this.urls.cliAuthConfirm,
3605
+ cliAuthConfirmUrl: this._getUrls().cliAuthConfirm,
3598
3606
  appUrl: options.appUrl,
3599
3607
  loginCode,
3600
3608
  });
@@ -4032,6 +4040,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
4032
4040
  ) => {
4033
4041
  return await this._interface.sendClientRequest(path, requestOptions, await this._getSession(), requestType);
4034
4042
  },
4043
+ getUrls: () => this._getUrls(),
4035
4044
  getRedirectMethod: () => this._redirectMethod ?? throwErr("Redirect method should have been initialized in the Stack client app constructor"),
4036
4045
  redirectToUrl: async (url: string | URL, options?: { replace?: boolean }) => {
4037
4046
  await this._redirectTo({ url, ...options });
@@ -391,6 +391,39 @@ describe("EventTracker", () => {
391
391
  }
392
392
  });
393
393
 
394
+ it("silently ignores network errors caused by ad blockers", async () => {
395
+ vi.useFakeTimers();
396
+ document.body.innerHTML = "<button>Click me</button>";
397
+
398
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
399
+ const sentBodies: string[] = [];
400
+ const tracker = new EventTracker({
401
+ projectId: "internal",
402
+ sendBatch: async (body) => {
403
+ sentBodies.push(body);
404
+ return Result.error(new TypeError("Failed to fetch"));
405
+ },
406
+ });
407
+
408
+ try {
409
+ tracker.start();
410
+
411
+ await advancePastFlush();
412
+ expect(sentBodies).toHaveLength(1);
413
+ expect(warnSpy).not.toHaveBeenCalled();
414
+
415
+ // Unlike ANALYTICS_NOT_ENABLED, ad blocker errors do NOT disable the
416
+ // tracker — subsequent flushes continue attempting delivery.
417
+ document.querySelector("button")?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
418
+ await advancePastFlush();
419
+ expect(sentBodies).toHaveLength(2);
420
+ expect(warnSpy).not.toHaveBeenCalled();
421
+ } finally {
422
+ tracker.stop();
423
+ warnSpy.mockRestore();
424
+ }
425
+ });
426
+
394
427
  it("silently disables when client interface returns ANALYTICS_NOT_ENABLED as an error", async () => {
395
428
  vi.useFakeTimers();
396
429
  document.body.innerHTML = "<button>Click me</button>";
@@ -8,7 +8,7 @@ import { cssEscapeIdent } from "@hexclave/shared/dist/utils/dom";
8
8
  import { buildElementsChain, ELEMENTS_CHAIN_MAX_DEPTH } from "@hexclave/shared/dist/utils/elements-chain";
9
9
  import { runAsynchronously } from "@hexclave/shared/dist/utils/promises";
10
10
  import { Result } from "@hexclave/shared/dist/utils/results";
11
- import { generateUuid, isAnalyticsNotEnabledError } from "./session-replay";
11
+ import { generateUuid, isAdBlockerNetworkError, isAnalyticsNotEnabledError } from "./session-replay";
12
12
 
13
13
  const FLUSH_INTERVAL_MS = 10_000;
14
14
  const MAX_EVENTS_PER_BATCH = 50;
@@ -511,6 +511,11 @@ export class EventTracker {
511
511
  this._disable();
512
512
  return;
513
513
  }
514
+ // Ad blockers commonly block analytics endpoints, causing network
515
+ // errors. These are expected and should not pollute the console.
516
+ if (isAdBlockerNetworkError(res.error)) {
517
+ return;
518
+ }
514
519
  console.warn("EventTracker flush failed:", res.error);
515
520
  return;
516
521
  }
@@ -49,6 +49,58 @@ describe("analytics option JSON conversion", () => {
49
49
  });
50
50
 
51
51
  describe("SessionRecorder flush", () => {
52
+ it("silently ignores network errors caused by ad blockers", async () => {
53
+ vi.useFakeTimers();
54
+
55
+ const storageKey = `hexclave:session-replay:v1:test-project`;
56
+ localStorage.setItem(storageKey, JSON.stringify({
57
+ session_id: "test-session",
58
+ created_at_ms: Date.now(),
59
+ last_activity_ms: Date.now(),
60
+ }));
61
+
62
+ const sentBodies: string[] = [];
63
+ const recorder = new SessionRecorder(
64
+ {
65
+ projectId: "test-project",
66
+ sendBatch: async (body) => {
67
+ sentBodies.push(body);
68
+ return Result.error(new TypeError("Failed to fetch"));
69
+ },
70
+ },
71
+ {},
72
+ );
73
+
74
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
75
+
76
+ try {
77
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
78
+ (recorder as any)._events = [{ type: 2, timestamp: Date.now(), data: {} }];
79
+
80
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
81
+ (recorder as any)._tick();
82
+ await vi.advanceTimersByTimeAsync(0);
83
+
84
+ expect(sentBodies).toHaveLength(1);
85
+ expect(warnSpy).not.toHaveBeenCalled();
86
+
87
+ // Unlike ANALYTICS_NOT_ENABLED, ad blocker errors do NOT disable the
88
+ // recorder — subsequent flushes continue attempting delivery.
89
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
90
+ (recorder as any)._events = [{ type: 3, timestamp: Date.now(), data: {} }];
91
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
92
+ (recorder as any)._tick();
93
+ await vi.advanceTimersByTimeAsync(0);
94
+ expect(sentBodies).toHaveLength(2);
95
+ expect(warnSpy).not.toHaveBeenCalled();
96
+ } finally {
97
+ recorder.stop();
98
+ warnSpy.mockRestore();
99
+ localStorage.removeItem(storageKey);
100
+ vi.useRealTimers();
101
+ }
102
+ });
103
+
52
104
  it("silently disables when client interface returns ANALYTICS_NOT_ENABLED as an error", async () => {
53
105
  vi.useFakeTimers();
54
106
 
@@ -170,6 +170,21 @@ export function isAnalyticsNotEnabledError(error: unknown): boolean {
170
170
  return KnownErrors.AnalyticsNotEnabled.isInstance(error);
171
171
  }
172
172
 
173
+ /**
174
+ * Whether the error looks like a network failure caused by an ad blocker or
175
+ * similar extension blocking analytics requests. These are expected in
176
+ * production and should be silently ignored rather than logged as warnings.
177
+ */
178
+ export function isAdBlockerNetworkError(error: unknown): boolean {
179
+ if (error instanceof Error) {
180
+ return error.message.includes("Failed to fetch")
181
+ || error.message.includes("NetworkError")
182
+ || error.message.includes("Load failed")
183
+ || error.message.includes("network connection");
184
+ }
185
+ return false;
186
+ }
187
+
173
188
  export class SessionRecorder {
174
189
  private _started = false;
175
190
  private _cancelled = false;
@@ -278,6 +293,11 @@ export class SessionRecorder {
278
293
  this._disable();
279
294
  return;
280
295
  }
296
+ // Ad blockers commonly block analytics endpoints, causing network
297
+ // errors. These are expected and should not pollute the console.
298
+ if (isAdBlockerNetworkError(res.error)) {
299
+ return;
300
+ }
281
301
  captureWarning("SessionRecorder.flush", res.error);
282
302
  return;
283
303
  }
@@ -13,6 +13,7 @@ import { AsyncStoreProperty, EmailConfig } from "../../common";
13
13
  import { AdminEmailOutbox, AdminSentEmail } from "../../email";
14
14
  import { InternalApiKey, InternalApiKeyCreateOptions, InternalApiKeyFirstView } from "../../internal-api-keys";
15
15
  import { AdminProjectPermission, AdminProjectPermissionDefinition, AdminProjectPermissionDefinitionCreateOptions, AdminProjectPermissionDefinitionUpdateOptions, AdminTeamPermission, AdminTeamPermissionDefinition, AdminTeamPermissionDefinitionCreateOptions, AdminTeamPermissionDefinitionUpdateOptions } from "../../permissions";
16
+ import type { PlanUsage } from "../../plan-usage";
16
17
  import { AdminProject } from "../../projects";
17
18
  import { _HexclaveAdminAppImpl } from "../implementations";
18
19
  import { StackServerApp, StackServerAppConstructorOptions } from "./server-app";
@@ -75,6 +76,7 @@ export type StackAdminAppConstructorOptions<HasTokenStore extends boolean, Proje
75
76
  /** @deprecated Use `HexclaveAdminApp` from the `@hexclave/*` package instead — same symbol, new brand name. See https://docs.hexclave.com/migration. */
76
77
  export type StackAdminApp<HasTokenStore extends boolean = boolean, ProjectId extends string = string> = (
77
78
  & AsyncStoreProperty<"project", [], AdminProject, false>
79
+ & AsyncStoreProperty<"planUsage", [], PlanUsage, false>
78
80
  & AsyncStoreProperty<"internalApiKeys", [], InternalApiKey[], true>
79
81
  & AsyncStoreProperty<"teamPermissionDefinitions", [], AdminTeamPermissionDefinition[], true>
80
82
  & AsyncStoreProperty<"projectPermissionDefinitions", [], AdminProjectPermissionDefinition[], true>
@@ -131,6 +131,7 @@ export type StackClientApp<HasTokenStore extends boolean = boolean, ProjectId ex
131
131
  sendAnalyticsEventBatch(body: string, options: { keepalive: boolean }): Promise<Result<Response, Error>>,
132
132
  addRequestListener(listener: RequestListener): () => void,
133
133
  sendRequest(path: string, requestOptions: RequestInit, requestType?: "client" | "server" | "admin"): Promise<Response>,
134
+ getUrls(): Readonly<ResolvedHandlerUrls>,
134
135
  getRedirectMethod(): RedirectMethod,
135
136
  redirectToUrl(url: string | URL, options?: { replace?: boolean }): Promise<void>,
136
137
  redirectToHandler(handlerName: keyof HandlerUrls, options?: RedirectToOptions): Promise<void>,
@@ -118,6 +118,14 @@ export type {
118
118
  PushedConfigSource
119
119
  } from "./projects";
120
120
 
121
+ export type {
122
+ PlanUsage,
123
+ PlanUsageKind,
124
+ PlanUsageNextPlanId,
125
+ PlanUsagePlanId,
126
+ PlanUsageRow,
127
+ } from "./plan-usage";
128
+
121
129
  export type {
122
130
  EditableTeamMemberProfile, ReceivedTeamInvitation,
123
131
  SentTeamInvitation, ServerListUsersOptions,
@@ -0,0 +1,29 @@
1
+
2
+ //===========================================
3
+ // THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template
4
+ //===========================================
5
+ export type PlanUsageKind = "current" | "metered" | "capability";
6
+ export type PlanUsagePlanId = "free" | "team" | "growth";
7
+ export type PlanUsageNextPlanId = "team" | "growth";
8
+
9
+ export type PlanUsageRow = {
10
+ itemId: string,
11
+ displayName: string,
12
+ kind: PlanUsageKind,
13
+ used: number | null,
14
+ limit: number | null,
15
+ remaining: number | null,
16
+ overage: number | null,
17
+ isUnlimited: boolean,
18
+ };
19
+
20
+ export type PlanUsage = {
21
+ ownerTeamId: string,
22
+ ownerTeamDisplayName: string,
23
+ planId: PlanUsagePlanId,
24
+ planDisplayName: string,
25
+ periodStart: Date,
26
+ periodEnd: Date,
27
+ nextPlanId: PlanUsageNextPlanId | null,
28
+ rows: PlanUsageRow[],
29
+ };
@@ -30,6 +30,8 @@ export type PushConfigOptions = {
30
30
  export type Project = {
31
31
  readonly id: string,
32
32
  readonly displayName: string,
33
+ readonly pushedConfigError: { message: string } | null,
34
+ readonly configWarnings: { message: string }[],
33
35
  readonly config: ProjectConfig,
34
36
  };
35
37
 
@@ -42,6 +44,7 @@ export type AdminProject = {
42
44
  readonly isDevelopmentEnvironment: boolean,
43
45
  readonly ownerTeamId: string | null,
44
46
  readonly onboardingStatus: ProjectOnboardingStatus,
47
+ readonly onboardingState: NonNullable<ProjectsCrud["Admin"]["Read"]["onboarding_state"]> | null,
45
48
  readonly logoUrl: string | null | undefined,
46
49
  readonly logoFullUrl: string | null | undefined,
47
50
  readonly logoDarkModeUrl: string | null | undefined,