@hexclave/js 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 (95) hide show
  1. package/dist/dev-tool/dev-tool-core.d.ts.map +1 -1
  2. package/dist/dev-tool/dev-tool-core.js +4 -68
  3. package/dist/dev-tool/dev-tool-core.js.map +1 -1
  4. package/dist/esm/dev-tool/dev-tool-core.d.ts.map +1 -1
  5. package/dist/esm/dev-tool/dev-tool-core.js +4 -68
  6. package/dist/esm/dev-tool/dev-tool-core.js.map +1 -1
  7. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +6 -0
  8. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  9. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js +31 -1
  10. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
  11. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -0
  12. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  13. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +18 -11
  14. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  15. package/dist/esm/lib/hexclave-app/apps/implementations/common.js +1 -1
  16. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.d.ts.map +1 -1
  17. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.js +2 -1
  18. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.js.map +1 -1
  19. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.test.js +26 -0
  20. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.test.js.map +1 -1
  21. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.d.ts +7 -1
  22. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.d.ts.map +1 -1
  23. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.js +11 -1
  24. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.js.map +1 -1
  25. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.test.js +43 -0
  26. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.test.js.map +1 -1
  27. package/dist/esm/lib/hexclave-app/apps/interfaces/admin-app.d.ts +2 -1
  28. package/dist/esm/lib/hexclave-app/apps/interfaces/admin-app.d.ts.map +1 -1
  29. package/dist/esm/lib/hexclave-app/apps/interfaces/admin-app.js.map +1 -1
  30. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts +1 -0
  31. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.d.ts.map +1 -1
  32. package/dist/esm/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  33. package/dist/esm/lib/hexclave-app/index.d.ts +2 -1
  34. package/dist/esm/lib/hexclave-app/plan-usage/index.d.ts +27 -0
  35. package/dist/esm/lib/hexclave-app/plan-usage/index.d.ts.map +1 -0
  36. package/dist/esm/lib/hexclave-app/plan-usage/index.js +1 -0
  37. package/dist/esm/lib/hexclave-app/projects/index.d.ts +7 -0
  38. package/dist/esm/lib/hexclave-app/projects/index.d.ts.map +1 -1
  39. package/dist/esm/lib/hexclave-app/projects/index.js.map +1 -1
  40. package/dist/esm/pushed-config-error-overlay/index.d.ts +7 -0
  41. package/dist/esm/pushed-config-error-overlay/index.d.ts.map +1 -0
  42. package/dist/esm/pushed-config-error-overlay/index.js +464 -0
  43. package/dist/esm/pushed-config-error-overlay/index.js.map +1 -0
  44. package/dist/index.d.ts +2 -1
  45. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts +6 -0
  46. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  47. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js +31 -1
  48. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
  49. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts +1 -0
  50. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  51. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +18 -11
  52. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  53. package/dist/lib/hexclave-app/apps/implementations/common.js +1 -1
  54. package/dist/lib/hexclave-app/apps/implementations/event-tracker.d.ts.map +1 -1
  55. package/dist/lib/hexclave-app/apps/implementations/event-tracker.js +1 -0
  56. package/dist/lib/hexclave-app/apps/implementations/event-tracker.js.map +1 -1
  57. package/dist/lib/hexclave-app/apps/implementations/event-tracker.test.js +26 -0
  58. package/dist/lib/hexclave-app/apps/implementations/event-tracker.test.js.map +1 -1
  59. package/dist/lib/hexclave-app/apps/implementations/session-replay.d.ts +7 -1
  60. package/dist/lib/hexclave-app/apps/implementations/session-replay.d.ts.map +1 -1
  61. package/dist/lib/hexclave-app/apps/implementations/session-replay.js +11 -0
  62. package/dist/lib/hexclave-app/apps/implementations/session-replay.js.map +1 -1
  63. package/dist/lib/hexclave-app/apps/implementations/session-replay.test.js +43 -0
  64. package/dist/lib/hexclave-app/apps/implementations/session-replay.test.js.map +1 -1
  65. package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts +2 -1
  66. package/dist/lib/hexclave-app/apps/interfaces/admin-app.d.ts.map +1 -1
  67. package/dist/lib/hexclave-app/apps/interfaces/admin-app.js.map +1 -1
  68. package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts +1 -0
  69. package/dist/lib/hexclave-app/apps/interfaces/client-app.d.ts.map +1 -1
  70. package/dist/lib/hexclave-app/apps/interfaces/client-app.js.map +1 -1
  71. package/dist/lib/hexclave-app/index.d.ts +2 -1
  72. package/dist/lib/hexclave-app/plan-usage/index.d.ts +27 -0
  73. package/dist/lib/hexclave-app/plan-usage/index.d.ts.map +1 -0
  74. package/dist/lib/hexclave-app/plan-usage/index.js +0 -0
  75. package/dist/lib/hexclave-app/projects/index.d.ts +7 -0
  76. package/dist/lib/hexclave-app/projects/index.d.ts.map +1 -1
  77. package/dist/lib/hexclave-app/projects/index.js.map +1 -1
  78. package/dist/pushed-config-error-overlay/index.d.ts +7 -0
  79. package/dist/pushed-config-error-overlay/index.d.ts.map +1 -0
  80. package/dist/pushed-config-error-overlay/index.js +466 -0
  81. package/dist/pushed-config-error-overlay/index.js.map +1 -0
  82. package/package.json +2 -2
  83. package/src/dev-tool/dev-tool-core.ts +5 -59
  84. package/src/lib/hexclave-app/apps/implementations/admin-app-impl.ts +40 -1
  85. package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +20 -11
  86. package/src/lib/hexclave-app/apps/implementations/event-tracker.test.ts +33 -0
  87. package/src/lib/hexclave-app/apps/implementations/event-tracker.ts +6 -1
  88. package/src/lib/hexclave-app/apps/implementations/session-replay.test.ts +52 -0
  89. package/src/lib/hexclave-app/apps/implementations/session-replay.ts +20 -0
  90. package/src/lib/hexclave-app/apps/interfaces/admin-app.ts +2 -0
  91. package/src/lib/hexclave-app/apps/interfaces/client-app.ts +1 -0
  92. package/src/lib/hexclave-app/index.ts +8 -0
  93. package/src/lib/hexclave-app/plan-usage/index.ts +29 -0
  94. package/src/lib/hexclave-app/projects/index.ts +3 -0
  95. package/src/pushed-config-error-overlay/index.ts +548 -0
@@ -23,6 +23,7 @@ import { EmailConfig, hexclaveAppInternalsSymbol } from "../../common";
23
23
  import { AdminEmailTemplate } from "../../email-templates";
24
24
  import { InternalApiKey, InternalApiKeyBase, InternalApiKeyBaseCrudRead, InternalApiKeyCreateOptions, InternalApiKeyFirstView, internalApiKeyCreateOptionsToCrud } from "../../internal-api-keys";
25
25
  import { AdminProjectPermission, AdminProjectPermissionDefinition, AdminProjectPermissionDefinitionCreateOptions, AdminProjectPermissionDefinitionUpdateOptions, AdminTeamPermission, AdminTeamPermissionDefinition, AdminTeamPermissionDefinitionCreateOptions, AdminTeamPermissionDefinitionUpdateOptions, adminProjectPermissionDefinitionCreateOptionsToCrud, adminProjectPermissionDefinitionUpdateOptionsToCrud, adminTeamPermissionDefinitionCreateOptionsToCrud, adminTeamPermissionDefinitionUpdateOptionsToCrud } from "../../permissions";
26
+ import type { PlanUsage } from "../../plan-usage";
26
27
  import { AdminOwnedProject, AdminProject, AdminProjectUpdateOptions, PushConfigOptions, adminProjectUpdateOptionsToCrud } from "../../projects";
27
28
  import type { AdminSessionReplay, AdminSessionReplayChunk, ListSessionReplayChunksOptions, ListSessionReplayChunksResult, ListSessionReplaysOptions, ListSessionReplaysResult, SessionReplayAllEventsResult } from "../../session-replays";
28
29
  import { ManagedEmailProviderListItem, ManagedEmailProviderSetupResult, ManagedEmailProviderStatus, EmailOutboxUpdateOptions, StackAdminApp, StackAdminAppConstructorOptions } from "../interfaces/admin-app";
@@ -35,6 +36,7 @@ import * as yup from "yup";
35
36
  import { PushedConfigSource } from "../../projects";
36
37
 
37
38
  type BranchConfigSourceApi = yup.InferType<typeof branchConfigSourceSchema>;
39
+ type PlanUsageResponse = Awaited<ReturnType<HexclaveAdminInterface["getPlanUsage"]>>;
38
40
  /**
39
41
  * Converts a PushedConfigSource (SDK camelCase) to BranchConfigSourceApi (API snake_case).
40
42
  */
@@ -77,6 +79,9 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
77
79
  private readonly _adminProjectCache = createCache(async () => {
78
80
  return await this._interface.getProject();
79
81
  });
82
+ private readonly _planUsageCache = createCache(async () => {
83
+ return await this._interface.getPlanUsage();
84
+ });
80
85
  private readonly _internalApiKeysCache = createCache(async () => {
81
86
  const res = await this._interface.listInternalApiKeys();
82
87
  return res;
@@ -194,10 +199,17 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
194
199
  isDevelopmentEnvironment: data.is_development_environment,
195
200
  ownerTeamId: data.owner_team_id,
196
201
  onboardingStatus: data.onboarding_status,
202
+ onboardingState: data.onboarding_state ?? null,
197
203
  logoUrl: data.logo_url,
198
204
  logoFullUrl: data.logo_full_url,
199
205
  logoDarkModeUrl: data.logo_dark_mode_url,
200
206
  logoFullDarkModeUrl: data.logo_full_dark_mode_url,
207
+ pushedConfigError: data.pushed_config_error == null ? null : {
208
+ message: data.pushed_config_error.message,
209
+ },
210
+ configWarnings: data.config_warnings.map((warning) => ({
211
+ message: warning.message,
212
+ })),
201
213
  config: {
202
214
  signUpEnabled: data.config.sign_up_enabled,
203
215
  credentialEnabled: data.config.credential_enabled,
@@ -322,6 +334,28 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
322
334
  };
323
335
  }
324
336
 
337
+ _planUsageFromCrud(data: PlanUsageResponse): PlanUsage {
338
+ return {
339
+ ownerTeamId: data.owner_team_id,
340
+ ownerTeamDisplayName: data.owner_team_display_name,
341
+ planId: data.plan_id,
342
+ planDisplayName: data.plan_display_name,
343
+ periodStart: new Date(data.period_start_millis),
344
+ periodEnd: new Date(data.period_end_millis),
345
+ nextPlanId: data.next_plan_id,
346
+ rows: data.rows.map((row) => ({
347
+ itemId: row.item_id,
348
+ displayName: row.display_name,
349
+ kind: row.kind,
350
+ used: row.used,
351
+ limit: row.limit,
352
+ remaining: row.remaining,
353
+ overage: row.overage,
354
+ isUnlimited: row.is_unlimited,
355
+ })),
356
+ };
357
+ }
358
+
325
359
  override async getProject(): Promise<AdminProject> {
326
360
  return this._adminProjectFromCrud(
327
361
  Result.orThrow(await this._adminProjectCache.getOrWait([], "write-only")),
@@ -330,6 +364,11 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
330
364
  }
331
365
 
332
366
 
367
+ async getPlanUsage(): Promise<PlanUsage> {
368
+ return this._planUsageFromCrud(Result.orThrow(await this._planUsageCache.getOrWait([], "write-only")));
369
+ }
370
+
371
+
333
372
  protected _createInternalApiKeyBaseFromCrud(data: InternalApiKeyBaseCrudRead): InternalApiKeyBase {
334
373
  const app = this;
335
374
  return {
@@ -1030,7 +1069,7 @@ export class _HexclaveAdminAppImplIncomplete<HasTokenStore extends boolean, Proj
1030
1069
 
1031
1070
 
1032
1071
  async getStripeAccountInfo(): Promise<null | { account_id: string, charges_enabled: boolean, details_submitted: boolean, payouts_enabled: boolean }> {
1033
- return await this._interface.getStripeAccountInfo();
1072
+ return Result.orThrow(await this._stripeAccountInfoCache.getOrWait([], "write-only"));
1034
1073
  }
1035
1074
 
1036
1075
 
@@ -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,12 +40,13 @@ 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 type * as yup from "yup";
46
+ import { envVars } from "../../../../generated/env";
46
47
  import { constructRedirectUrl } from "../../../../utils/url";
47
48
  import { callOAuthCallback, getNewOAuthProviderOrScopeUrl } from "../../../auth";
48
49
  import { CookieHelper, createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookie, deleteCookieClient, getCookieClient, isSecure as isSecureCookieContext, saveVerifierAndState, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie";
49
- import { envVars } from "../../../../generated/env";
50
50
  import { ApiKey, ApiKeyCreationOptions, ApiKeyUpdateOptions, apiKeyCreationOptionsToCrud } from "../../api-keys";
51
51
  import { ConvexCtx, GetCurrentPartialUserOptions, GetCurrentUserOptions, HandlerUrlOptions, HandlerUrls, OAuthScopesOnSignIn, RedirectMethod, RedirectToOptions, RequestLike, ResolvedHandlerUrls, TokenStoreInit, hexclaveAppInternalsSymbol } from "../../common";
52
52
  import { DeprecatedOAuthConnection, OAuthConnection } from "../../connected-accounts";
@@ -69,6 +69,7 @@ import { AnalyticsOptions, SessionRecorder, analyticsOptionsFromJson, analyticsO
69
69
 
70
70
  import { mountClickmapOverlay } from "../../../../clickmap";
71
71
  import { mountDevTool } from "../../../../dev-tool";
72
+ import { mountPushedConfigErrorOverlay } from "../../../../pushed-config-error-overlay";
72
73
 
73
74
  let isReactServer = false;
74
75
 
@@ -344,7 +345,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
344
345
  {
345
346
  provider,
346
347
  redirectUrl: this._getOAuthCallbackRedirectUri(),
347
- errorRedirectUrl: this.urls.error,
348
+ errorRedirectUrl: this._getUrls().error,
348
349
  providerScope: mergeScopeStrings(scopeString, (this._oauthScopesOnSignIn[provider as ProviderType] ?? []).join(" ")),
349
350
  },
350
351
  session,
@@ -546,7 +547,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
546
547
  {
547
548
  provider: options.providerId,
548
549
  redirectUrl: this._getOAuthCallbackRedirectUri(),
549
- errorRedirectUrl: this.urls.error,
550
+ errorRedirectUrl: this._getUrls().error,
550
551
  providerScope: mergeScopeStrings(options.scope || "", (this._oauthScopesOnSignIn[options.providerId] ?? []).join(" ")),
551
552
  },
552
553
  options.session,
@@ -712,6 +713,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
712
713
  // when a dashboard-minted token is handed over, so the listener is
713
714
  // mounted unconditionally (the heavy UI is lazy-loaded on demand).
714
715
  mountClickmapOverlay(this as any);
716
+ mountPushedConfigErrorOverlay(this as any);
715
717
  }
716
718
  }
717
719
 
@@ -823,7 +825,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
823
825
 
824
826
  protected _getOAuthCallbackRedirectUri(): string {
825
827
  if (!this._isOAuthCallbackUrlHosted()) {
826
- return this.urls.oauthCallback;
828
+ return this._getUrls().oauthCallback;
827
829
  }
828
830
  if (typeof window === "undefined") {
829
831
  throw new HexclaveAssertionError("Hosted OAuth callback URLs require a browser environment to use the current URL as the redirect URI");
@@ -1571,6 +1573,12 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
1571
1573
  return {
1572
1574
  id: crud.id,
1573
1575
  displayName: crud.display_name,
1576
+ pushedConfigError: crud.pushed_config_error == null ? null : {
1577
+ message: crud.pushed_config_error.message,
1578
+ },
1579
+ configWarnings: crud.config_warnings.map((warning) => ({
1580
+ message: warning.message,
1581
+ })),
1574
1582
  config: {
1575
1583
  signUpEnabled: crud.config.sign_up_enabled,
1576
1584
  credentialEnabled: crud.config.credential_enabled,
@@ -2749,13 +2757,13 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
2749
2757
  async redirectToMfa(options?: RedirectToOptions) { return await this._redirectToHandler("mfa", options); }
2750
2758
 
2751
2759
  async sendForgotPasswordEmail(email: string, options?: { callbackUrl?: string }): Promise<Result<undefined, KnownErrors["UserNotFound"]>> {
2752
- return await this._interface.sendForgotPasswordEmail(email, options?.callbackUrl ?? constructRedirectUrl(this.urls.passwordReset, "callbackUrl"));
2760
+ return await this._interface.sendForgotPasswordEmail(email, options?.callbackUrl ?? constructRedirectUrl(this._getUrls().passwordReset, "callbackUrl"));
2753
2761
  }
2754
2762
 
2755
2763
  async sendMagicLinkEmail(email: string, options?: {
2756
2764
  callbackUrl?: string,
2757
2765
  }): Promise<Result<{ nonce: string }, KnownErrors["RedirectUrlNotWhitelisted"] | KnownErrors["BotChallengeFailed"]>> {
2758
- const callbackUrl = options?.callbackUrl ?? constructRedirectUrl(this.urls.magicLinkCallback, "callbackUrl");
2766
+ const callbackUrl = options?.callbackUrl ?? constructRedirectUrl(this._getUrls().magicLinkCallback, "callbackUrl");
2759
2767
  return await this._executeResultWithBotChallengeFlow({
2760
2768
  action: "send_magic_link_email",
2761
2769
  execute: async (challenge) => {
@@ -2965,7 +2973,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
2965
2973
  return await this._interface.authorizeOAuth({
2966
2974
  provider,
2967
2975
  redirectUrl: constructRedirectUrl(this._getOAuthCallbackRedirectUri(), "redirectUrl"),
2968
- errorRedirectUrl: constructRedirectUrl(this.urls.error, "errorRedirectUrl"),
2976
+ errorRedirectUrl: constructRedirectUrl(this._getUrls().error, "errorRedirectUrl"),
2969
2977
  afterCallbackRedirectUrl,
2970
2978
  type: "authenticate",
2971
2979
  providerScope: this._oauthScopesOnSignIn[provider]?.join(" "),
@@ -3085,7 +3093,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
3085
3093
  }
3086
3094
  this._ensurePersistentTokenStore();
3087
3095
  const session = await this._getSession();
3088
- const emailVerificationRedirectUrl = options.noVerificationCallback ? undefined : options.verificationCallbackUrl ?? constructRedirectUrl(this.urls.emailVerification, "verificationCallbackUrl");
3096
+ const emailVerificationRedirectUrl = options.noVerificationCallback ? undefined : options.verificationCallbackUrl ?? constructRedirectUrl(this._getUrls().emailVerification, "verificationCallbackUrl");
3089
3097
 
3090
3098
  const executeSignUp = async (challenge: { token?: string, phase?: "invisible" | "visible", unavailable?: true }) => {
3091
3099
  let result = await this._interface.signUpWithCredential(
@@ -3248,7 +3256,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
3248
3256
 
3249
3257
  // Step 2: Open the browser for the user to authenticate and display the verification code
3250
3258
  const url = buildCliAuthConfirmUrl({
3251
- cliAuthConfirmUrl: this.urls.cliAuthConfirm,
3259
+ cliAuthConfirmUrl: this._getUrls().cliAuthConfirm,
3252
3260
  appUrl: options.appUrl,
3253
3261
  loginCode,
3254
3262
  });
@@ -3645,6 +3653,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
3645
3653
  ) => {
3646
3654
  return await this._interface.sendClientRequest(path, requestOptions, await this._getSession(), requestType);
3647
3655
  },
3656
+ getUrls: () => this._getUrls(),
3648
3657
  getRedirectMethod: () => this._redirectMethod ?? throwErr("Redirect method should have been initialized in the Stack client app constructor"),
3649
3658
  redirectToUrl: async (url: string | URL, options?: { replace?: boolean }) => {
3650
3659
  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>
@@ -123,6 +123,7 @@ export type StackClientApp<HasTokenStore extends boolean = boolean, ProjectId ex
123
123
  sendAnalyticsEventBatch(body: string, options: { keepalive: boolean }): Promise<Result<Response, Error>>,
124
124
  addRequestListener(listener: RequestListener): () => void,
125
125
  sendRequest(path: string, requestOptions: RequestInit, requestType?: "client" | "server" | "admin"): Promise<Response>,
126
+ getUrls(): Readonly<ResolvedHandlerUrls>,
126
127
  getRedirectMethod(): RedirectMethod,
127
128
  redirectToUrl(url: string | URL, options?: { replace?: boolean }): Promise<void>,
128
129
  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,