@hexclave/tanstack-start 1.0.23 → 1.0.25

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 (80) hide show
  1. package/dist/dev-tool/dev-tool-core.d.ts.map +1 -1
  2. package/dist/dev-tool/dev-tool-core.js +3 -67
  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 +3 -67
  6. package/dist/esm/dev-tool/dev-tool-core.js.map +1 -1
  7. package/dist/esm/generated/env.js +20 -20
  8. package/dist/esm/generated/env.js.map +1 -1
  9. package/dist/esm/lib/auth.js +2 -2
  10. package/dist/esm/lib/auth.js.map +1 -1
  11. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  12. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js +2 -0
  13. package/dist/esm/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
  14. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  15. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js +9 -3
  16. package/dist/esm/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  17. package/dist/esm/lib/hexclave-app/apps/implementations/common.js +1 -1
  18. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.d.ts.map +1 -1
  19. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.js +2 -1
  20. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.js.map +1 -1
  21. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.test.js +26 -0
  22. package/dist/esm/lib/hexclave-app/apps/implementations/event-tracker.test.js.map +1 -1
  23. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.d.ts +7 -1
  24. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.d.ts.map +1 -1
  25. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.js +11 -1
  26. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.js.map +1 -1
  27. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.test.js +43 -0
  28. package/dist/esm/lib/hexclave-app/apps/implementations/session-replay.test.js.map +1 -1
  29. package/dist/esm/lib/hexclave-app/projects/index.d.ts +6 -0
  30. package/dist/esm/lib/hexclave-app/projects/index.d.ts.map +1 -1
  31. package/dist/esm/lib/hexclave-app/projects/index.js.map +1 -1
  32. package/dist/esm/lib/hexclave-app/url-targets.test.js +7 -7
  33. package/dist/esm/lib/hexclave-app/url-targets.test.js.map +1 -1
  34. package/dist/esm/pushed-config-error-overlay/index.d.ts +7 -0
  35. package/dist/esm/pushed-config-error-overlay/index.d.ts.map +1 -0
  36. package/dist/esm/pushed-config-error-overlay/index.js +464 -0
  37. package/dist/esm/pushed-config-error-overlay/index.js.map +1 -0
  38. package/dist/generated/env.js +20 -20
  39. package/dist/generated/env.js.map +1 -1
  40. package/dist/lib/auth.js +2 -2
  41. package/dist/lib/auth.js.map +1 -1
  42. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.d.ts.map +1 -1
  43. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js +2 -0
  44. package/dist/lib/hexclave-app/apps/implementations/admin-app-impl.js.map +1 -1
  45. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.d.ts.map +1 -1
  46. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js +9 -3
  47. package/dist/lib/hexclave-app/apps/implementations/client-app-impl.js.map +1 -1
  48. package/dist/lib/hexclave-app/apps/implementations/common.js +1 -1
  49. package/dist/lib/hexclave-app/apps/implementations/event-tracker.d.ts.map +1 -1
  50. package/dist/lib/hexclave-app/apps/implementations/event-tracker.js +1 -0
  51. package/dist/lib/hexclave-app/apps/implementations/event-tracker.js.map +1 -1
  52. package/dist/lib/hexclave-app/apps/implementations/event-tracker.test.js +26 -0
  53. package/dist/lib/hexclave-app/apps/implementations/event-tracker.test.js.map +1 -1
  54. package/dist/lib/hexclave-app/apps/implementations/session-replay.d.ts +7 -1
  55. package/dist/lib/hexclave-app/apps/implementations/session-replay.d.ts.map +1 -1
  56. package/dist/lib/hexclave-app/apps/implementations/session-replay.js +11 -0
  57. package/dist/lib/hexclave-app/apps/implementations/session-replay.js.map +1 -1
  58. package/dist/lib/hexclave-app/apps/implementations/session-replay.test.js +43 -0
  59. package/dist/lib/hexclave-app/apps/implementations/session-replay.test.js.map +1 -1
  60. package/dist/lib/hexclave-app/projects/index.d.ts +6 -0
  61. package/dist/lib/hexclave-app/projects/index.d.ts.map +1 -1
  62. package/dist/lib/hexclave-app/projects/index.js.map +1 -1
  63. package/dist/lib/hexclave-app/url-targets.test.js +7 -7
  64. package/dist/lib/hexclave-app/url-targets.test.js.map +1 -1
  65. package/dist/pushed-config-error-overlay/index.d.ts +7 -0
  66. package/dist/pushed-config-error-overlay/index.d.ts.map +1 -0
  67. package/dist/pushed-config-error-overlay/index.js +466 -0
  68. package/dist/pushed-config-error-overlay/index.js.map +1 -0
  69. package/package.json +3 -3
  70. package/src/dev-tool/dev-tool-core.ts +4 -58
  71. package/src/lib/auth.ts +2 -2
  72. package/src/lib/hexclave-app/apps/implementations/admin-app-impl.ts +6 -0
  73. package/src/lib/hexclave-app/apps/implementations/client-app-impl.ts +11 -3
  74. package/src/lib/hexclave-app/apps/implementations/event-tracker.test.ts +33 -0
  75. package/src/lib/hexclave-app/apps/implementations/event-tracker.ts +6 -1
  76. package/src/lib/hexclave-app/apps/implementations/session-replay.test.ts +52 -0
  77. package/src/lib/hexclave-app/apps/implementations/session-replay.ts +20 -0
  78. package/src/lib/hexclave-app/projects/index.ts +2 -0
  79. package/src/lib/hexclave-app/url-targets.test.ts +7 -7
  80. package/src/pushed-config-error-overlay/index.ts +548 -0
@@ -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";
@@ -42,14 +41,15 @@ import { BotChallengeExecutionFailedError, BotChallengeUserCancelledError, withB
42
41
  import { createUrlIfValid, getRelativePart, isRelative } from "@hexclave/shared/dist/utils/urls";
43
42
  import { generateUuid } from "@hexclave/shared/dist/utils/uuids";
44
43
  import * as tanstackStartServerContext from "@hexclave/tanstack-start/tanstack-start-server-context"; // THIS_LINE_PLATFORM tanstack-start
44
+ import { WebAuthnError, startAuthentication, startRegistration } from "@simplewebauthn/browser";
45
45
  import * as TanStackRouter from "@tanstack/react-router"; // THIS_LINE_PLATFORM tanstack-start
46
46
  import * as cookie from "cookie";
47
47
  import React, { useCallback, useMemo } from "react"; // THIS_LINE_PLATFORM react-like
48
48
  import type * as yup from "yup";
49
+ import { envVars } from "../../../../generated/env";
49
50
  import { constructRedirectUrl } from "../../../../utils/url";
50
51
  import { callOAuthCallback, getNewOAuthProviderOrScopeUrl } from "../../../auth";
51
52
  import { CookieHelper, createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookie, deleteCookieClient, getCookieClient, isSecure as isSecureCookieContext, saveVerifierAndState, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie";
52
- import { envVars } from "../../../../generated/env";
53
53
  import { ApiKey, ApiKeyCreationOptions, ApiKeyUpdateOptions, apiKeyCreationOptionsToCrud } from "../../api-keys";
54
54
  import { ConvexCtx, GetCurrentPartialUserOptions, GetCurrentUserOptions, HandlerUrlOptions, HandlerUrls, OAuthScopesOnSignIn, RedirectMethod, RedirectToOptions, RequestLike, ResolvedHandlerUrls, TokenStoreInit, hexclaveAppInternalsSymbol } from "../../common";
55
55
  import { DeprecatedOAuthConnection, OAuthConnection } from "../../connected-accounts";
@@ -73,6 +73,7 @@ import { AnalyticsOptions, SessionRecorder, analyticsOptionsFromJson, analyticsO
73
73
  import { useAsyncCache } from "./common";
74
74
  import { mountClickmapOverlay } from "../../../../clickmap";
75
75
  import { mountDevTool } from "../../../../dev-tool";
76
+ import { mountPushedConfigErrorOverlay } from "../../../../pushed-config-error-overlay";
76
77
 
77
78
  let isReactServer = false;
78
79
 
@@ -742,6 +743,7 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
742
743
  // when a dashboard-minted token is handed over, so the listener is
743
744
  // mounted unconditionally (the heavy UI is lazy-loaded on demand).
744
745
  mountClickmapOverlay(this as any);
746
+ mountPushedConfigErrorOverlay(this as any);
745
747
  }
746
748
  }
747
749
 
@@ -1636,6 +1638,12 @@ export class _HexclaveClientAppImplIncomplete<HasTokenStore extends boolean, Pro
1636
1638
  return {
1637
1639
  id: crud.id,
1638
1640
  displayName: crud.display_name,
1641
+ pushedConfigError: crud.pushed_config_error == null ? null : {
1642
+ message: crud.pushed_config_error.message,
1643
+ },
1644
+ configWarnings: crud.config_warnings.map((warning) => ({
1645
+ message: warning.message,
1646
+ })),
1639
1647
  config: {
1640
1648
  signUpEnabled: crud.config.sign_up_enabled,
1641
1649
  credentialEnabled: crud.config.credential_enabled,
@@ -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
  }
@@ -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
 
@@ -85,7 +85,7 @@ describe("handler URL targets", () => {
85
85
  });
86
86
 
87
87
  it("uses hosted defaults for unspecified URLs", () => {
88
- vi.stubEnv("NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX", ".example-stack-hosted.test");
88
+ vi.stubEnv("NEXT_PUBLIC_HEXCLAVE_HOSTED_HANDLER_DOMAIN_SUFFIX", ".example-stack-hosted.test");
89
89
 
90
90
  const urls = resolveHandlerUrls({
91
91
  projectId: "project-id",
@@ -101,7 +101,7 @@ describe("handler URL targets", () => {
101
101
  });
102
102
 
103
103
  it("keeps redirect-only post-auth targets local even when the default target is hosted", () => {
104
- vi.stubEnv("NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX", ".example-stack-hosted.test");
104
+ vi.stubEnv("NEXT_PUBLIC_HEXCLAVE_HOSTED_HANDLER_DOMAIN_SUFFIX", ".example-stack-hosted.test");
105
105
 
106
106
  const urls = resolveHandlerUrls({
107
107
  projectId: "project-id",
@@ -145,7 +145,7 @@ describe("handler URL targets", () => {
145
145
  });
146
146
 
147
147
  it("inherits a hosted default target for the OAuth callback", () => {
148
- vi.stubEnv("NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX", ".example-stack-hosted.test");
148
+ vi.stubEnv("NEXT_PUBLIC_HEXCLAVE_HOSTED_HANDLER_DOMAIN_SUFFIX", ".example-stack-hosted.test");
149
149
 
150
150
  const urls = resolveHandlerUrls({
151
151
  projectId: "project-id",
@@ -183,7 +183,7 @@ describe("handler URL targets", () => {
183
183
  });
184
184
 
185
185
  it("uses default target for unknown /handler/* pages", () => {
186
- vi.stubEnv("NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX", ".example-stack-hosted.test");
186
+ vi.stubEnv("NEXT_PUBLIC_HEXCLAVE_HOSTED_HANDLER_DOMAIN_SUFFIX", ".example-stack-hosted.test");
187
187
 
188
188
  const url = resolveUnknownHandlerPathFallbackUrl({
189
189
  defaultTarget: { type: "hosted" },
@@ -195,7 +195,7 @@ describe("handler URL targets", () => {
195
195
  });
196
196
 
197
197
  it("uses the full hosted handler URL template when configured", () => {
198
- vi.stubEnv("NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE", "http://{projectId}.localhost:${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}09/{hostedPath}");
198
+ vi.stubEnv("NEXT_PUBLIC_HEXCLAVE_HOSTED_HANDLER_URL_TEMPLATE", "http://{projectId}.localhost:${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}09/{hostedPath}");
199
199
  vi.stubEnv("NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX", "93");
200
200
 
201
201
  const urls = resolveHandlerUrls({
@@ -210,7 +210,7 @@ describe("handler URL targets", () => {
210
210
  });
211
211
 
212
212
  it("validates the hosted handler URL template placeholders", () => {
213
- vi.stubEnv("NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE", "http://localhost:9309/{projectId}/handler");
213
+ vi.stubEnv("NEXT_PUBLIC_HEXCLAVE_HOSTED_HANDLER_URL_TEMPLATE", "http://localhost:9309/{projectId}/handler");
214
214
 
215
215
  expect(() => resolveHandlerUrls({
216
216
  projectId: "project-id",
@@ -221,7 +221,7 @@ describe("handler URL targets", () => {
221
221
  });
222
222
 
223
223
  it("rejects hosted handler URL templates that put the project ID in the path", () => {
224
- vi.stubEnv("NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE", "http://localhost:9309/{projectId}/{hostedPath}");
224
+ vi.stubEnv("NEXT_PUBLIC_HEXCLAVE_HOSTED_HANDLER_URL_TEMPLATE", "http://localhost:9309/{projectId}/{hostedPath}");
225
225
 
226
226
  expect(() => resolveHandlerUrls({
227
227
  projectId: "project-id",